Чат-Карта OpenAPI: /pos учётная система

API «Чат-Карты» для своей интеграции

Документ для разработчиков, которые подключают к сервису «Чат-Карта» учётную или кассовую систему: 1С:Розница или другую конфигурацию 1С, товароучётную программу, кассовое ПО или собственный бэкенд. Интеграция идёт только через это открытое API, готового расширения «для всех» нет: у каждой базы свои доработки.

С тарифа «Базовый» доступны примеры кода для 1С:Розница из папки onec/ (выдаём с тарифа «Базовый»). Они реализуют ровно этот API, их встраивают в свою конфигурацию как образец.

Условия подключения: самостоятельно по API — 0 ₽; интеграция под ключ — цена индивидуально; при оплате тарифа «Бизнес» или «Сеть / PRO» от 6 месяцев помощь с интеграцией бесплатна.

Машиночитаемые спецификации OpenAPI 3.1:

Содержание

  1. Как устроена интеграция
  2. Подключение
  3. Общие правила
  4. Коды на кассе
  5. Часть A. Методы сервиса для вашей системы (/pos/*)
  6. Часть B. Методы, которые реализует ваша система
  7. Настройки программы лояльности
  8. Тарифы и функции
  9. Вебхуки
  10. Типовые сценарии на кассе
  11. Проверка интеграции
  12. Чек-лист перед запуском

Как устроена интеграция

 Покупатель (MAX / Telegram)                Ваша система (учёт, касса)
        │ mini-app                                   ▲   │
        ▼                                            │   │
 ┌──────────────────────────┐   Часть B: сервис → вы │   │ Часть A: вы → сервис
 │  Чат-Карта               │ ───────────────────────┘   │  /pos/*
 │  app.chat-card.ru        │ ◄──────────────────────────┘
 └──────────────────────────┘

Ответственность разделена так:

Что Где хранится и кто отвечает
Клиенты, карты, баланс баллов, начисление и списание Ваша система. Сервис баланс не хранит, только кэширует для показа
Привязка карты к аккаунту MAX или Telegram, согласие на рассылку Сервис
Настройки программы (уровни, день рождения, автовозврат, сгорание, подарки, история, оценка визита, «Приведи друга», новый статус) Сервис. Ваша система читает их и может менять
Рассылки, поздравления, напоминания о сгорании Сервис отправляет через ботов. Баллы по этим сценариям начисляет ваша система по запросу сервиса
Одноразовые коды списания и подарков Выдаёт сервис, касса гасит их через API
Покупки для статистики, RFM-сегментов, оценок визита и наград за друзей Ваша система присылает после каждого чека
Статусы (уровни) карт Ваша система. Сервис узнаёт о смене из card_status в покупке или POST /pos/card-status
Оценки визитов, приглашения друзей, сегменты Сервис. Ваша система может забрать их через /pos/reviews, /pos/referrals, /pos/segments или вебхуки

Интеграция двусторонняя:

Минимальная интеграция, при которой работает карта в приложении: из части B нужны POST /register, GET /balance, GET /welcome-bonus, GET /statuses; из части A — POST /pos/redeem/consume. Остальные методы включают дополнительные возможности, список в чек-листе.

Подключение

  1. Организация регистрируется на chat-card.ru и в кабинете открывает раздел «Подключение». Этот документ опубликован на сайте: chat-card.ru/developers.

  2. Адрес вашей системы для части B. Блок «1С:Розница» с полями «Адрес HTTP-сервиса», «Пользователь 1С» и «Пароль» подходит для любой системы, не только для 1С. Адрес — это база вашего HTTP-сервиса, например https://erp.example.ru/hs/loyalty. Сервис добавляет к нему пути (/balance, /register, …). Требования:

    • только https:// с действующим сертификатом;
    • адрес должен быть виден из интернета, внутренние адреса (10.x, 192.168.x и т. п.) не принимаются;
    • пароль хранится зашифрованным и после сохранения не показывается.

    Кнопка «Проверить связь» делает GET <адрес>/welcome-bonus с Basic-авторизацией и показывает результат.

  3. Ключ кассы для части A. Там же нажмите «Выпустить ключ кассы» и получите строку вида ck_…. Ваша система передаёт её в заголовке X-Api-Key. Ключ определяет организацию: все данные и коды привязаны к ней. Новый ключ отменяет старый.

  4. Адрес сервиса: https://app.chat-card.ru. Все методы части A начинаются с /pos/.

Пока адрес вашей системы не указан, сервис работает в демо-режиме со встроенной заглушкой. Так можно проверить mini-app и кабинет до готовности интеграции.

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

Формат.

Телефон везде приводится к виду 79991234567: 11 цифр, первая 7, без +.

Ошибки части A. Код HTTP и тело {"error": "<код>"}. При ошибках проверки данных добавляется errors: массив понятных текстов на русском, их можно показать пользователю.

HTTP error Что значит
400 invalid, bad_request, bad_purchase, too_many Неверные данные. Повтор с теми же данными не поможет
403 forbidden Нет ключа, ключ неверный или отозван, организация не активна (например, не оплачена)
403 plan Функция не входит в тариф организации, текст в message. См. Тарифы и функции
404 not_found Объект не найден или принадлежит другой организации
409 already_sent Действие невозможно в текущем состоянии
410 token_invalid_or_expired Одноразовый код истёк, уже использован или выдан для другой организации
5xx — Временная ошибка сервиса, повторите позже

Повторы. При сетевой ошибке, 5xx и 429 повторяйте запрос с паузой. При 400, 403, 404, 409, 410 не повторяйте. POST /pos/purchases можно повторять сколько угодно, дублей не будет. Погашение кодов (…/consume) повторять нельзя: код уже погашен, повтор вернёт 410. Если ответ потерялся, попросите покупателя показать новый код.

Таймауты. Сервис ждёт ответа вашей системы 5 секунд. Отвечайте быстро: тяжёлые расчёты (например, сгорание по всей базе для /expiring) кэшируйте.

Коды на кассе

Сканер кассы может считать четыре вида кодов. Префикс говорит, что делать:

Код Вид Откуда Что делает касса
Номер карты, например 2200000000015 (EAN-13 или ваш формат) штрих-код пластиковая карта Находит карту у себя. Начислять можно, списывать нельзя
LC + номер карты: LC2200000000015 Code-128 экран карты в mini-app Отрезает LC, находит карту. Начислять можно, списывать нельзя. Покупка считается «картой из приложения»
LR + 14 цифр: LR48201937465012 QR кнопка «Списать баллы» в mini-app Гасит код через POST /pos/redeem/consume и получает номер карты. В этом чеке разрешено списание. Код живёт 60 секунд
LG + 14 цифр: LG90318274650193 QR вкладка «Подарки» в mini-app Гасит код через POST /pos/gifts/consume, выдаёт подарок и списывает его стоимость в баллах. Код живёт 15 минут

Списание только по одноразовому коду защищает от оплаты баллами по чужому скриншоту карты: код одноразовый и быстро истекает. Для QR на кассе нужен 2D-сканер. Под QR код показан и цифрами для ручного ввода.

Регулярные выражения: ^LC\w{1,40}$, ^LR\d{14}$, ^LG\d{14}$.


Часть A. Методы сервиса для вашей системы (/pos/*)

Базовый адрес https://app.chat-card.ru, заголовок X-Api-Key: ck_… обязателен во всех запросах.

Метод Назначение
GET /pos/plan Тариф, лимиты, включённые функции
POST /pos/redeem/consume Погасить код списания LR…
POST /pos/gifts/consume Погасить код подарка LG…
GET /pos/gifts Каталог подарков
POST /pos/purchases Сообщить о покупке или возврате
POST /pos/card-status Сообщить новые статусы карт
GET /pos/reviews Оценки визитов после покупки (PRO)
GET /pos/referrals «Приведи друга»: приглашения и награды (PRO)
GET /pos/segments RFM-сегменты карт (PRO)
GET /pos/loyalty-settings Прочитать настройки программы
PUT /pos/loyalty-settings Изменить настройки программы
GET /pos/sources, PUT /pos/sources Точки регистрации (магазины, QR)
POST /pos/campaigns Создать push-рассылку
GET /pos/campaigns/{id} Статус и статистика рассылки
POST /pos/campaigns/{id}/cancel Отменить рассылку

GET /pos/plan

Тариф организации, его лимиты и включённые функции, а также текущее использование. Удобно показать в своей форме, что доступно, и не вызывать методы, которые ответят 403. Для дополнительного юрлица или базы действует тариф основной организации.

200

{
  "plan": {
    "id": "business", "name": "Бизнес", "tagline": "Оптимальный",
    "price": { "1": 4990, "6": 4490, "12": 3990 },
    "limits": { "cards": null, "designs": null, "sources": 5, "campaigns_per_month": null, "users": 5, "orgs": 1 },
    "features": { "push_filters": true, "push_schedule": true, "segments": false, "history": true, "gifts": true,
                  "birthday": true, "winback": true, "review": false, "referral": false, "webhooks": true, "...": "..." }
  },
  "usage": { "cards": 812, "sources": 3, "campaigns": { "month": "2026-10", "used": 6, "limit": null } }
}

null в limits и campaigns.limit — без ограничения. Список функций — в разделе Тарифы и функции.

POST /pos/redeem/consume

Касса отсканировала QR LR…. Сервис гасит код и возвращает карту, по которой в этом чеке разрешено списание баллов.

POST /pos/redeem/consume
X-Api-Key: ck_...
Content-Type: application/json

{ "token": "LR48201937465012", "register_id": "kassa-2", "store_id": "kazan-mega" }

register_id и store_id необязательны, сервис только записывает их в журнал.

200

{ "card_code": "2200000000015", "phone": "79991234567", "platform": "max", "user_id": "123456789" }

410 token_invalid_or_expired: код истёк (60 с), уже погашен, неверного формата или выдан другой организации. Попросите покупателя нажать «Списать баллы» ещё раз.

Код одноразовый: после ответа 200 его больше нет. Держите разрешение на списание только в текущем чеке. Сумму списания и проверку остатка делает ваша система.

POST /pos/gifts/consume

Касса отсканировала QR LG…. Сервис гасит код и сообщает, какой подарок выдать и сколько баллов списать.

{ "token": "LG90318274650193", "register_id": "kassa-2", "store_id": "kazan-mega" }

200

{
  "card_code": "2200000000015",
  "gift": { "id": 3, "name": "Кофе в подарок", "points": 150, "sku": "A-1" }
}

410 — код недействителен. Повторное сканирование того же кода тоже вернёт 410. 403 plan — подарки за баллы не входят в тариф (они доступны с тарифа «Бизнес»).

GET /pos/gifts

Каталог подарков организации, включая скрытые. Подходит, например, чтобы сопоставить подарки с товарами.

200

{ "gifts": [
  { "id": 3, "name": "Кофе в подарок", "description": "Любой напиток 0,3 л",
    "points": 150, "sku": "A-1", "image_asset_id": 12, "image_url": "/media/12",
    "active": true, "sort": 0, "kind": "gift", "partner": "", "coupon_code": "" },
  { "id": 7, "name": "Скидка 20% в кофейне «Зерно»", "description": "", "points": 200, "sku": "",
    "image_asset_id": null, "image_url": null, "active": true, "sort": 1,
    "kind": "coupon", "partner": "Кофейня «Зерно»", "coupon_code": "ZERNO20" }
] }

image_url указан относительно https://app.chat-card.ru.

kind: gift — подарок, который выдаёт касса по коду LG…; coupon — купон партнёра (тариф «Сеть / PRO»). Купон на кассу не приходит: покупатель обменивает баллы на промокод coupon_code в приложении, а баллы сервис списывает у вас через POST /bonus/writeoff. coupon_code есть только в этом методе, в приложении промокод показывается после списания.

POST /pos/purchases

Сообщите о покупке после проведения чека с картой лояльности. Из этих данных строится статистика в кабинете («покупки картой из приложения») и сценарий автовозврата: клиент считается давно не покупавшим по дате последней присланной покупки.

{
  "receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d",
  "card_code": "2200000000015",
  "amount": 6490.00,
  "scanned_code": "LC2200000000015",
  "store_code": "kazan-mega",
  "purchased_at": "2026-10-19T14:05:00+03:00"
}
Поле Обязательно Описание
receipt_id да Уникальный ID чека, до 100 символов [A-Za-z0-9_.:/-]. Повтор с тем же ID не создаёт дубль
card_code да Номер карты, до 40 символов; префикс LC можно не отрезать
amount да Сумма чека в рублях. Для возврата — отрицательная
scanned_code нет Что отсканировал кассир. Если начинается с LC или LR, покупка считается «картой из приложения»
source нет app или card, если scanned_code не передаёте. По умолчанию card
store_code нет Код точки из /pos/sources, до 40 символов
purchased_at нет Время чека, по умолчанию время запроса
card_status нет Статус карты после этого чека, как name в GET /statuses, до 100 символов. Если чек повысил статус, на тарифе «Сеть / PRO» покупатель сразу получит поздравление. См. POST /pos/card-status

Можно отправлять пачкой до 500 чеков: { "purchases": [ {...}, {...} ] }.

200 { "recorded": true, "created": 1 }, где created — сколько чеков записано впервые. 400 bad_purchase с полем index — номер неверной записи в пачке. Пачка не записывается целиком.

Отправляйте покупки из очереди в фоне: закрытие чека не должно ждать сервис.

Для каждого нового чека (не повтора receipt_id) сервис дополнительно: обновляет статус карты по card_status, планирует просьбу оценить визит (review) и, если это первая покупка приглашённого друга, начисляет баллы за «Приведи друга» (referral). Поэтому не меняйте receipt_id при повторной отправке: новый ID — это новый чек.

POST /pos/card-status

Сообщите статусы карт, если они меняются в вашей системе не только при чеке: регламентный пересчёт уровней, ручная смена, перевыпуск. Можно одной картой { "card_code": "...", "status": "..." } или пачкой до 1000:

{ "cards": [ { "card_code": "2200000000015", "status": "Золотая" }, { "card_code": "2200000000022", "status": "Серебряная" } ] }

status — как name в вашем GET /statuses, до 100 символов. Учитываются только карты, подключённые к приложению.

200 { "changed": 2, "upgraded": 1 } · 400 bad_card с index или too_many.

Что делает сервис при изменении статуса:

Какой статус старше, сервис узнаёт по status_upgrade.order (от младшего к старшему), а если он пуст — по cashback_percent из GET /statuses. Первый известный сервису статус карты поздравления не даёт: это не повышение, а начальное значение.

Если статус меняется только при проведении чека, отдельный вызов не нужен: передайте card_status в POST /pos/purchases.

GET /pos/reviews

Оценки визитов после покупки (тариф «Сеть / PRO», раздел настроек review). Через delay_min минут после чека из POST /pos/purchases бот просит оценить визит от 1 до 5. Оценки 1–3 уходят владельцу в кабинет, на почту (с телефоном покупателя) и вебхуком review.negative, в интернет они не попадают. Покупателю с оценкой 5 бот предлагает оставить отзыв в Яндекс Картах или 2ГИС и начисляет public_bonus баллов через ваш POST /bonus (reason: "review").

GET /pos/reviews?cursor=1760873472000_381&max_rating=3

Параметр Описание
cursor Значение next_cursor из прошлого ответа; при первой синхронизации пустое (cursor=). Оценки идут в порядке времени оценки: покупатель может оценить визит через несколько дней после покупки, поэтому синхронизация по номеру оценки теряла бы такие оценки
since_id Устаревший вариант: последние 500 оценок с id больше этого. Для новых интеграций используйте cursor
max_rating Только оценки не выше этой, например 3 — только плохие

200 — до 500 оценок по времени оценки, next_cursor для следующего запроса и has_more (есть ещё — запросите сразу):

{ "reviews": [
  { "id": 381, "rating": 2, "comment": "Долго ждали на кассе", "amount_rub": 6490, "purchased_at": "2026-10-19T11:05:00.000Z",
    "can_rate": false, "public": null, "card_code": "2200000000015", "receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d",
    "store_code": "kazan-mega", "rated_at": "2026-10-19T11:31:12.000Z", "public_target": null }
], "next_cursor": "1760873472000_381", "has_more": false }

Для оценки 5 в public приходит { "bonus": 100, "targets": [{ "id": "yandex", "title": "Яндекс Карты", "url": "https://…" }], "done": true }, где done — покупатель перешёл по ссылке, а public_target — куда именно (yandex или 2gis). 403 plan — функция не входит в тариф.

GET /pos/referrals

«Приведи друга» (тариф «Сеть / PRO», раздел referral). У каждого покупателя в приложении своя ссылка-приглашение. Засчитывается только друг, которого ещё не было в вашей системе (created: true в POST /register). После первой покупки друга на кассе (POST /pos/purchases на сумму от referral.min_amount) сервис вызывает ваш POST /bonus с reason: "referral" дважды: для друга (friend_points, по умолчанию 300) и для пригласившего (referrer_points). Ключи: referral-friend-<id> и referral-referrer-<id>.

GET /pos/referrals или GET /pos/referrals?referrer_card=2200000000015

200

{ "referrals": [
  { "id": 17, "referrer_card": "2200000000015", "friend_card": "2200000000046", "status": "rewarded",
    "receipt_id": "0012-000351", "created_at": "2026-10-12T08:00:00.000Z", "rewarded_at": "2026-10-14T15:20:00.000Z" }
] }

status: registered — друг подключил карту, покупки ещё не было; rewarded — баллы начислены обоим; failed — ваш POST /bonus не прошёл, причина в журнале кабинета. 403 plan.

GET /pos/segments

RFM-сегменты карт (тариф «Сеть / PRO»). Сервис считает их по покупкам из POST /pos/purchases для карт, подключённых к приложению: R — дней с последней покупки, F — число покупок, M — сумма. Сегмент можно записать в свойство клиента у себя или использовать в рассылке (audience.segment).

200

{
  "segments": [ { "id": "vip", "title": "VIP", "hint": "Топ-20% по сумме покупок, покупали за последние 45 дней" }, "..." ],
  "cards": [ { "card_code": "2200000000015", "segment": "vip_leaving", "recency_days": 63, "frequency": 9, "monetary_rub": 54200 } ]
}
id Сегмент
vip Топ-20% по сумме (и от 2 покупок), покупали за последние 45 дней
vip_leaving Топ-20% по сумме, не покупали от 45 до 180 дней
loyal 3 покупки и больше, последняя — не позже 90 дней назад
active 2 покупки, последняя — не позже 90 дней назад
new Подключили карту или впервые купили за последние 30 дней
one_time Купили один раз от 30 до 180 дней назад
sleeping Не покупали от 90 до 180 дней
lost Не покупали больше 180 дней
no_purchases Подключили карту больше 30 дней назад, покупок нет

recency_days — null, если покупок нет. 403 plan.

GET /pos/loyalty-settings

Настройки программы лояльности. Сервис — главный источник настроек: их меняют в кабинете или через PUT. Ваша система читает их, чтобы считать начисление по уровням, задержку и срок жизни баллов, бонус за регистрацию. Читайте раз в 5–15 минут и кэшируйте. Сервис всегда отдаёт все разделы, отсутствующие значения заполняет значениями по умолчанию. Разделы функций, которых нет в тарифе, приходят с enabled: false, а в поле plan_features — какие функции включены в тариф (как plan.features в GET /pos/plan).

200 — объект с разделами, полное описание в разделе Настройки программы лояльности.

PUT /pos/loyalty-settings

Изменить настройки, например со своей формы настроек. Передавайте только разделы, которые меняете. Остальные останутся как были. Внутри раздела тоже можно передать часть полей.

{ "birthday": { "enabled": true, "days_before": 3, "points": [ { "status": "", "amount": 300 }, { "status": "Золотая", "amount": 1000 } ] } }

200 — полные настройки после сохранения. 400 invalid, errors: ["День рождения, за сколько дней: целое число от 0 до 30"] — ничего не сохранено. Включить функцию не из тарифа нельзя: errors: ["«Приведи друга»: доступно на тарифе «Сеть / PRO» и выше"]. Выключить её и поменять тексты можно. Поле plan_features в запросе игнорируется.

GET /pos/sources

Точки регистрации: города и магазины, у каждой своя QR-ссылка на mini-app. Клиент, который оформил карту по ней, записывается на эту точку.

200

{ "sources": [ { "code": "kazan-mega", "city": "Казань", "name": "ТЦ Мега", "active": true, "origin": "1c" } ] }

origin: 1c — точку прислала ваша система, cabinet — её завели в кабинете.

PUT /pos/sources

Создать или обновить точки по коду. Точек, которых нет в запросе, запрос не касается. Чтобы скрыть точку, передайте active: false.

{ "sources": [ { "code": "kazan-mega", "city": "Казань", "name": "ТЦ Мега", "active": true } ] }

200 { "saved": 1 } · 400 invalid с index и errors · 400 plan — активных точек получится больше, чем разрешает тариф (limits.sources), ничего не сохранено.

Код точки приходит вам при регистрации клиента (source_code в POST /register).

POST /pos/campaigns

Создать push-рассылку в MAX и Telegram. Сервис сам отправит её в send_at, держать вашу систему включённой не нужно. Получат её только клиенты, которые дали согласие на уведомления и не заблокировали бота.

{
  "title": "Скидка 20% на осеннюю коллекцию",
  "body": "Только до воскресенья в магазинах Казани.",
  "button_text": "Открыть карту",
  "image_url": "https://example.ru/autumn.jpg",
  "send_at": "2026-10-24T10:00:00+03:00",
  "ends_at": "2026-10-27T23:59:00+03:00",
  "audience": {
    "platforms": ["max", "telegram"],
    "statuses": ["Золотая"],
    "sources": ["kazan-mega"],
    "client_ids": ["2200000000015", "79991234567", "LC2200000000016"]
  }
}

Рассылка по RFM-сегменту (тариф «Сеть / PRO»):

{ "title": "Скучаем по вам", "body": "Дарим двойные баллы до конца недели.", "audience": { "segment": "vip_leaving" } }
Поле Ограничения
title обязательно, до 100 символов
body обязательно, до 3000 символов, с картинкой до 900 (ограничение мессенджеров)
button_text до 40 символов
image_url только https://
send_at когда отправить; если не указано, отправка сразу
ends_at до какого времени акция видна в ленте mini-app
audience.platforms max, telegram; пусто — оба
audience.statuses статусы карт, как в GET /statuses; пусто — все
audience.sources коды точек; неизвестный код — ошибка 400
audience.client_ids до 5000: номера карт (можно с LC), телефоны в любом виде, ID пользователей мессенджера
audience.segment RFM-сегмент из GET /pos/segments, например vip_leaving. Список карт сегмента берётся в момент создания и заменяет client_ids

Условия внутри audience складываются через «И»: статус и точка и список клиентов.

Ограничения тарифа (ответ 400 invalid, текст в errors):

201 { "id": 42, "status": "scheduled", "send_at": "2026-10-24T07:00:00.000Z", "recipients": 318 }, где recipients — сколько человек получат рассылку на текущий момент. 400 invalid с errors.

Рассылка создаётся сразу запланированной. Изменить её нельзя, только отменить и создать новую.

GET /pos/campaigns/{id}

200

{ "id": 42, "status": "sent", "send_at": "…", "sent_at": "…", "recipients": 318,
  "stats": { "pending": 0, "sent": 301, "failed": 2, "blocked": 15 } }

Возможные status: scheduled (ждёт времени), sending, sent, cancelled. В stats: blocked — пользователь заблокировал бота, failed — мессенджер не принял сообщение.

POST /pos/campaigns/{id}/cancel

200 { "id": 42, "status": "cancelled" } · 409 already_sent · 404 — рассылка другой организации или не найдена.


Часть B. Методы, которые реализует ваша система

Сервис вызывает их по адресу из кабинета, например https://erp.example.ru/hs/loyalty/balance?card=…, с заголовком Authorization: Basic <логин:пароль в base64> и Accept: application/json. Таймаут 5 секунд.

Метод Когда вызывается Обязателен
POST /register Покупатель оформляет карту в mini-app да
GET /balance Открытие карты, обновление баланса (кэш 90 с), выдача кода подарка да
GET /welcome-bonus Экран регистрации (кэш 5 мин), кнопка «Проверить связь» да
GET /statuses Кабинет: оформление карт и фильтр рассылок по статусам да
POST /bonus День рождения, автовозврат, «Приведи друга», отзыв, новый статус если включены эти сценарии
POST /bonus/writeoff Купон партнёра за баллы если есть купоны партнёров
GET /expiring Напоминание о сгорании баллов если включено напоминание
GET /history Вкладка «История» в mini-app (кэш 60 с) если включена история
GET /card Поиск карты по телефону желательно, зарезервирован

Если метод не нужен, отвечайте 404: сервис покажет «недоступно», но работать продолжит.

Объект card в ответах:

{ "code": "2200000000015", "id": "b3a1…", "owner_name": "Ольга Иванова", "kind": "Бонусная карта", "status": "Серебряная", "birth_date": "1990-05-17" }
Поле Описание
code Номер карты: его покажет приложение в штрих-коде и пришлёт касса. До 40 символов [A-Za-z0-9_]
id Внутренний ID карты в вашей системе, строка
owner_name Имя владельца для карты в приложении. Первое слово подставляется в {имя} в поздравлениях
kind Название вида карты, показывается, если организация не загрузила логотип
status Статус или уровень карты. Совпадает с name в /statuses: по нему выбирается оформление и фильтруются рассылки
birth_date YYYY-MM-DD или null

POST /register

Покупатель поделился номером телефона в мессенджере, сервис проверил подпись номера. Найдите клиента по телефону или создайте нового и выпустите карту. Метод должен быть идемпотентным: повторный вызов с тем же телефоном возвращает ту же карту.

{
  "phone": "79991234567",
  "first_name": "Ольга",
  "last_name": "Иванова",
  "channel": "max",
  "channel_user_id": "123456789",
  "source_code": "kazan-mega",
  "source_name": "Казань, ТЦ Мега",
  "birth_date": "1990-05-17",
  "card_kind": "Золотая"
}

source_code, source_name, birth_date, card_kind приходят, только если известны. card_kind — вид (статус) карты из вашего GET /statuses, который магазин выбрал для новых клиентов: выпустите новую карту этого вида. Клиенту, у которого карта уже есть, вид не меняйте. Пусто — вид по вашим настройкам. Дата рождения проверена сервисом: настоящая дата, возраст от 5 до 120 лет. Записывайте её, только если у клиента в вашей системе дата ещё пуста.

200

{ "card": { ... }, "created": true, "welcome_bonus": { "amount": 200, "status": "accrued" } }

Правила бонуса за регистрацию, которые должна соблюдать ваша система:

GET /balance

GET /balance?card=2200000000015

200

{
  "balance": 850,
  "available": 850,
  "status": "Серебряная",
  "active": true,
  "cashback_percent": 5,
  "cashback_total": 12850,
  "birth_date": "1990-05-17",
  "expiring": { "amount": 300, "date": "2026-11-02" },
  "pending": 120
}
Поле Описание
balance Всего баллов на карте
available Сколько можно потратить сейчас. По нему проверяется, хватает ли на подарок
status Текущий статус карты (может меняться в вашей системе)
active false — карта заблокирована, приложение покажет «обратитесь в магазин»
cashback_percent Процент начисления для этой карты или null
cashback_total Сколько баллов начислено за всё время или null
birth_date Дата рождения, YYYY-MM-DD или null. Если она появилась у вас позже, сервис сохранит её для поздравлений
expiring Ближайшее сгорание {amount, date} или null. В приложении: «300 баллов сгорят 2 ноября»
pending Баллы за покупки, которые ещё не начислены из-за задержки, или 0

404 — карта не найдена.

Если ваша система недоступна, приложение покажет последний известный баланс с пометкой «обновляем».

GET /welcome-bonus

200 { "enabled": true, "amount": 200, "existing_clients": false, "lifetime_days": 30 }

Сервис показывает эти условия на экране регистрации («Дарим 200 баллов за регистрацию»). Если организация сохранила раздел welcome в кабинете, на экране показываются условия из кабинета. Начисление в любом случае делает ваша система в POST /register.

GET /statuses

200 — массив верхнего уровня:

[ { "name": "Серебряная", "cashback_percent": 5 }, { "name": "Золотая", "cashback_percent": 10 } ]

POST /bonus

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

{
  "card": "2200000000015",
  "amount": 300,
  "lifetime_days": 14,
  "reason": "birthday",
  "key": "birthday-2026-2200000000015",
  "comment": "Подарок на день рождения"
}
Поле Описание
amount Сколько баллов начислить, больше 0
lifetime_days Через сколько дней баллы сгорают, 0 — без срока
reason birthday — день рождения, winback — автовозврат, referral — «Приведи друга» (другу и пригласившему), review — отзыв на картах после оценки 5, status_upgrade — новый статус карты. Неизвестную причину лучше принимать как обычное начисление, а не отвечать 400
key Ключ идемпотентности, до 200 символов. Обязательно храните ключи: при повторе с тем же ключом не начисляйте второй раз
comment Текст для истории операций

200 { "accrued": true, "balance": 1150 }, при повторе ключа — { "accrued": true, "duplicate": true, "balance": 1150 }. 404 — карты нет. 409 card_inactive — карта заблокирована.

Сервис повторяет неудачный вызов до 5 раз при следующих запусках планировщика (раз в 15 минут), всегда с тем же key. Сообщение клиенту уходит только после успешного начисления.

Ключи по сценариям: referral-friend-<id> и referral-referrer-<id> (id из GET /pos/referrals), review-<id> (id из GET /pos/reviews), status-<статус>-<карта>. Для «Приведи друга», отзыва и нового статуса сервис не повторяет вызов по расписанию: если ваша система не ответила, награда помечается неудачной, ошибка видна в журнале кабинета, и баллы можно начислить вручную.

POST /bonus/writeoff

Списать баллы без кассы. Сейчас сервис вызывает его для купонов партнёров (тариф «Сеть / PRO»): покупатель обменивает баллы на промокод партнёра прямо в приложении, на кассу он не приходит.

{
  "card": "2200000000015",
  "amount": 200,
  "reason": "coupon",
  "key": "coupon-7-2200000000015-29342811",
  "comment": "Купон: Кофейня «Зерно» −20%"
}
Поле Описание
amount Сколько баллов списать, больше 0
reason Сейчас только coupon
key Ключ идемпотентности, до 200 символов. Храните ключи, как в POST /bonus: повтор с тем же ключом не списывает второй раз
comment Текст для истории операций (type: "redeem" в GET /history)

200 { "written_off": true, "balance": 650 }, при повторе ключа — { "written_off": true, "duplicate": true, "balance": 650 }. 409 { "error": "not_enough_points" } — баллов не хватает: покупатель увидит «Недостаточно баллов». 409 card_inactive — карта заблокирована. 404 — карты нет.

Проверяйте остаток и списывайте под блокировкой: сервис проверил баланс по GET /balance перед вызовом, но между проверкой и списанием баллы могли потратить на кассе. Если метод не реализован (404), купоны за баллы не выдаются, бесплатные купоны (points: 0) работают без него.

GET /expiring

GET /expiring?days=7 — у каких карт сгорают баллы в ближайшие days дней (от 1 до 365).

200 — массив верхнего уровня:

[ { "card": "2200000000015", "amount": 300, "date": "2026-11-02" } ]

На каждую карту одна строка с ближайшей датой сгорания и суммой на эту дату. Достаточно отдавать карты, подключённые к приложению. Порог «сгорает от N баллов» применяет сервис. Напоминание отправляется один раз на каждую пару «карта + дата».

GET /history

GET /history?card=2200000000015&limit=50 (limit от 1 до 200).

200

{ "items": [
  { "date": "2026-10-19T09:12:00", "type": "bonus", "amount": 300, "comment": "Подарок на день рождения", "pending": false, "expires": "2026-11-02" },
  { "date": "2026-10-12T18:40:11", "type": "redeem", "amount": -500, "comment": "Чек 0012-000345", "pending": false, "expires": null },
  { "date": "2026-10-12T18:40:11", "type": "accrual", "amount": 324.5, "comment": "Покупка в ТЦ Мега", "pending": true, "expires": "2027-10-12" }
] }
Поле Описание
date Дата и время операции
type accrual — за покупку, redeem — списание, burn — сгорание, bonus — подарочные баллы (регистрация, ДР, автовозврат), gift — подарок за баллы
amount Плюс — начисление, минус — списание
comment Что увидит покупатель; если пусто, приложение подпишет операцию по type
pending Начислится позже (задержка начисления)
expires До какой даты действуют эти баллы, или null

Сортировка: новые сверху. 404 — карты нет.

GET /card

GET /card?phone=79991234567 → 200 { "card": { ... } } или 404. Сейчас сервис этот метод не вызывает, он зарезервирован для поиска карты без регистрации.


Настройки программы лояльности

Объект из GET /pos/loyalty-settings со значениями по умолчанию. Применяет их частично сервис, частично ваша система: это указано в комментариях.

{
  // Начисление за покупку. Применяет ВАША СИСТЕМА при проведении чека
  "accrual": {
    "enabled": false,
    "levels": [                                   // до 50 строк
      { "status": "", "from_sum": 0, "percent": 3 },        // "" = для всех статусов
      { "status": "", "from_sum": 10000, "percent": 5 },    // чек от 10 000 ₽ — 5%
      { "status": "Золотая", "from_sum": 0, "percent": 7 }
    ],
    "delay_days": 0,        // 0..365: баллы становятся доступны через N дней (защита от возвратов)
    "lifetime_days": 0      // 0..3650: баллы за покупки сгорают через N дней, 0 — не сгорают
  },
  // Бонус за регистрацию. Применяет ВАША СИСТЕМА в POST /register
  "welcome": { "enabled": false, "amount": 0, "lifetime_days": 0, "existing_clients": false },
  // День рождения. Применяет СЕРВИС: в нужный день вызывает POST /bonus и пишет клиенту
  "birthday": {
    "enabled": false,
    "days_before": 0,       // 0..30: 0 — в день рождения
    "send_hour": 10,        // 0..23, час по Москве
    "lifetime_days": 14,
    "points": [ { "status": "", "amount": 300 }, { "status": "Золотая", "amount": 1000 } ],
    "text": "{имя}, с днём рождения! Дарим вам {баллы} баллов, ими можно оплатить покупку."
  },
  // Автовозврат. Применяет СЕРВИС по покупкам из POST /pos/purchases
  "winback": {
    "enabled": false, "after_days": 60, "repeat_days": 90, "send_hour": 12, "lifetime_days": 14,
    "points": [], "text": "{имя}, давно вас не видели! Дарим {баллы} баллов на следующую покупку."
  },
  // Напоминание о сгорании. Применяет СЕРВИС по GET /expiring
  "expiry": {
    "enabled": false, "days_before": 7, "send_hour": 12, "min_amount": 1,
    "text": "{имя}, {дата} сгорят {баллы} баллов. Успейте потратить их на покупку."
  },
  "gifts":   { "enabled": false },   // вкладка «Подарки» в приложении
  "history": { "enabled": false },   // вкладка «История» в приложении (GET /history)
  "birth_date": { "ask": "off" },    // off | optional | required — спрашивать ли дату рождения при регистрации
  "new_card": { "kind": "" },        // вид карты для клиента, которого нет в учётной системе ('' — по её настройкам)
  // Оценка визита (PRO). Применяет СЕРВИС по покупкам из POST /pos/purchases
  "review": {
    "enabled": false,
    "delay_min": 15,            // 1..1440: через сколько минут после чека спросить
    "repeat_days": 7,           // 0..365: не чаще раза в N дней на карту
    "min_amount": 0,            // от суммы чека, ₽
    "text": "Спасибо за покупку, {имя}! Оцените, пожалуйста, визит от 1 до 5.",
    "public_bonus": 100,        // баллы за переход к отзыву после оценки 5 (POST /bonus, reason review)
    "public_lifetime_days": 30,
    "yandex_url": "", "gis_url": "",   // https://… страницы отзывов; пусто — кнопки нет
    "notify_email": ""          // куда слать оценки 1–3; пусто — email организации
  },
  // «Приведи друга» (PRO). Применяет СЕРВИС после первой покупки друга
  "referral": {
    "enabled": false, "friend_points": 300, "referrer_points": 300, "lifetime_days": 60, "min_amount": 0,
    "friend_text": "{имя}, спасибо за первую покупку! Дарим {баллы} баллов от друга.",
    "referrer_text": "{имя}, ваш друг совершил первую покупку. Дарим вам {баллы} баллов!"
  },
  // Поздравление с новым статусом (PRO). Применяет СЕРВИС по card_status
  "status_upgrade": {
    "enabled": false,
    "points": [],               // баллы по статусу, как в birthday; пусто — только поздравление
    "lifetime_days": 30,
    "order": [],                // статусы от младшего к старшему; пусто — по cashback_percent из GET /statuses
    "text": "{имя}, поздравляем! Ваша карта теперь «{статус}». Кэшбэк и привилегии уже действуют."
  },
  // Только в ответе GET: какие функции входят в тариф
  "plan_features": { "birthday": true, "winback": true, "review": false, "...": "..." }
}

Уровни начисления. Для чека выбирайте строки со статусом карты (или пустым статусом, если для статуса строк нет) и берите строку с наибольшим from_sum, не превышающим сумму чека. Начислите сумма × percent / 100, округление на ваше усмотрение. Если accrual.enabled = true, отключите собственные правила начисления, иначе баллы начислятся дважды. При возврате уменьшайте начисление пропорционально.

Баллы по статусу (points в birthday и winback): строка с точным статусом карты, иначе строка с пустым статусом. Если подходящей строки нет, баллы не начисляются, но сообщение уходит.

Подстановки в текстах: {имя} — первое слово owner_name, {баллы} — количество, {дата} — дата дня рождения или сгорания («2 ноября»), {статус} — новый статус карты. Текст до 1000 символов.


Тарифы и функции

Что из API доступно организации, зависит от её тарифа. Узнать тариф и включённые функции можно методом GET /pos/plan, а в GET /pos/loyalty-settings они приходят в поле plan_features.

Функция (features.*) Старт Базовый Бизнес Сеть / PRO Где в API
Карт в базе (limits.cards) до 1 000 ∞ ∞ ∞ регистрация новых карт
Точек регистрации (limits.sources) 1 2 5 ∞ PUT /pos/sources
Рассылок в месяц (limits.campaigns_per_month) 2 4 ∞ ∞ POST /pos/campaigns
Доступов в кабинет (limits.users) / юрлиц или баз (limits.orgs) 1 / 1 2 / 1 5 / 1 20 / 3 кабинет
welcome — бонус за регистрацию ✓ ✓ ✓ ✓ POST /register
birthday — день рождения — ✓ ✓ ✓ POST /bonus birthday
onec_examples — примеры кода для 1С — ✓ ✓ ✓ папка onec/
push_filters, push_schedule — фильтры и расписание рассылок — — ✓ ✓ audience.*, send_at
winback — возврат спящих — — ✓ ✓ POST /bonus winback
history — история баллов — — ✓ ✓ GET /history
gifts — подарки за баллы — — ✓ ✓ POST /pos/gifts/consume
webhooks — вебхуки — — ✓ ✓ Вебхуки
segments — RFM-сегменты — — — ✓ GET /pos/segments, audience.segment
expiry — напоминание о сгорании — — — ✓ GET /expiring
review — оценка после покупки — — — ✓ GET /pos/reviews, POST /bonus review
referral — «Приведи друга» — — — ✓ GET /pos/referrals, POST /bonus referral
status_upgrade — поздравление с новым статусом — — — ✓ POST /pos/card-status, card_status, POST /bonus status_upgrade
coupons — купоны партнёров — — — ✓ POST /bonus/writeoff
receipts — детализация чеков в истории — — — ✓ по POST /pos/purchases
app_code — код покупки из приложения (LC) — — — ✓ касса
white_label, roles, analytics — — — ✓ кабинет и приложение

Открытое API (части A и B) доступно на всех тарифах.

Ошибки тарифа.

Вебхуки

Тариф «Бизнес» и выше. Сервис сообщает о событиях на ваш адрес сразу, без опроса. Адрес и события настраиваются в кабинете: «Подключение → Вебхуки» (только https://, адрес должен быть виден из интернета). Секрет подписи whsec_… показывается один раз, при первом сохранении и при перевыпуске. Кнопка «Проверить» отправляет тестовое card.registered с "test": true.

POST https://erp.example.ru/hooks/chat-card
Content-Type: application/json
X-ChatCard-Event: review.negative
X-ChatCard-Signature: sha256=5d41402abc4b2a76b9719d911017c592...

{ "event": "review.negative", "org_id": 12, "created_at": "2026-10-20T09:15:00.000Z",
  "data": { "review_id": 381, "card_code": "2200000000015", "rating": 2, "comment": "Долго ждали на кассе",
            "receipt_id": "7f3c2a10-5b1e-4c2d-9a77-0e1f2a3b4c5d", "store_code": "kazan-mega",
            "phone": "79991234567", "owner_name": "Ольга Иванова" } }
Событие Когда data
card.registered Покупатель подключил карту в приложении card_code, platform, created (карта выпущена сейчас), source_code, referred (пришёл по приглашению)
card.status_changed Изменился статус карты card_code, previous, status
review.created Покупатель оценил визит review_id, card_code, rating, comment, receipt_id, store_code
review.negative Оценка 1–3 (вместе с review.created) то же плюс phone, owner_name
referral.registered Друг подключил карту по приглашению referral_id, referrer_card, friend_card
referral.rewarded Первая покупка друга, баллы начислены referral_id, referrer_card, friend_card, receipt_id, friend_points, referrer_points

Правила доставки:

Проверка подписи. X-ChatCard-Signature = sha256= + HMAC-SHA256 от сырого тела запроса (байты как пришли, до разбора JSON) с ключом — строкой секрета whsec_… в UTF-8, в нижнем регистре hex. Сравнивайте за постоянное время и отклоняйте запрос с неверной подписью.

Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody — Buffer тела запроса, например express.raw({ type: 'application/json' })
export function verifyChatCard(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(String(header || ''));
  return a.length === b.length && timingSafeEqual(a, b);
}

1С (HTTP-сервис, платформа 8.3.11 и новее; в платформе нет готовой функции HMAC, поэтому она собрана из ХешированиеДанных):

Функция ПодписьВебхукаВерна(Запрос, Секрет) Экспорт
    Тело = Запрос.ПолучитьТелоКакДвоичныеДанные();
    Ключ = ПолучитьДвоичныеДанныеИзСтроки(Секрет, КодировкаТекста.UTF8);
    Ожидаемая = "sha256=" + НРег(ПолучитьHexСтрокуИзДвоичныхДанных(HMAC_SHA256(Ключ, Тело)));
    Пришла = НРег(СокрЛП(Запрос.Заголовки.Получить("X-ChatCard-Signature")));
    Возврат Ожидаемая = Пришла;
КонецФункции

Функция HMAC_SHA256(Знач Ключ, Данные)
    РазмерБлока = 64;
    Если Ключ.Размер() > РазмерБлока Тогда
        Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);
        Хеш.Добавить(Ключ);
        Ключ = Хеш.ХешСумма;
    КонецЕсли;
    БуферКлюча = Новый БуферДвоичныхДанных(РазмерБлока);
    БуферКлюча.Записать(0, ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Ключ));
    Внутр = Новый БуферДвоичныхДанных(РазмерБлока);
    Внеш = Новый БуферДвоичныхДанных(РазмерБлока);
    Для Индекс = 0 По РазмерБлока - 1 Цикл
        Байт = БуферКлюча.Получить(Индекс);
        Внутр.Установить(Индекс, ПобитовоеИсключительноеИли(Байт, 54)); // 0x36
        Внеш.Установить(Индекс, ПобитовоеИсключительноеИли(Байт, 92)); // 0x5C
    КонецЦикла;
    ХешВнутр = Новый ХешированиеДанных(ХешФункция.SHA256);
    ХешВнутр.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Внутр));
    ХешВнутр.Добавить(Данные);
    ХешВнеш = Новый ХешированиеДанных(ХешФункция.SHA256);
    ХешВнеш.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Внеш));
    ХешВнеш.Добавить(ХешВнутр.ХешСумма);
    Возврат ХешВнеш.ХешСумма;
КонецФункции

Если имя заголовка в Запрос.Заголовки у вас приходит в другом регистре, ищите его без учёта регистра. Секрет храните в константе с доступом только для администратора.


Типовые сценарии на кассе

Чек с начислением.

  1. Кассир сканирует карту: пластик, LC… или LR….
  2. Для LC… отрежьте префикс и найдите карту. Для LR… вызовите POST /pos/redeem/consume.
  3. Проведите чек, начислите баллы по своим правилам или по accrual.
  4. Положите покупку в очередь и отправьте POST /pos/purchases с scanned_code в фоне.

Оплата баллами.

  1. Покупатель жмёт в приложении «Списать баллы» и показывает QR LR….
  2. Касса вызывает POST /pos/redeem/consume и получает card_code.
  3. Только в этом чеке разрешите оплату баллами по этой карте. Пластик и LC… списание не разрешают.

Подарок за баллы.

  1. Покупатель выбирает подарок в приложении и показывает QR LG….
  2. Касса вызывает POST /pos/gifts/consume, получает card_code и gift.
  3. Проверьте остаток, спишите gift.points, выдайте подарок (по sku, если указан).
  4. При ошибке списания скажите кассиру и не выдавайте подарок. Код уже погашен, покупатель получит новый в приложении.

Возврат. Отправьте POST /pos/purchases с отрицательной amount и новым receipt_id и скорректируйте начисленные баллы у себя.

Статус карты повысился в чеке. Передайте новый статус в card_status той же покупки. Если статусы пересчитываются регламентным заданием, отправьте изменившиеся карты пачкой в POST /pos/card-status.

Проверка интеграции

  1. Заведите организацию в кабинете и подключите бота MAX или Telegram («Подключение»).

  2. Укажите адрес вашей системы и нажмите «Проверить связь». Должен пройти GET /welcome-bonus.

  3. Выпустите ключ кассы и проверьте часть A:

    curl -s https://app.chat-card.ru/pos/loyalty-settings -H "X-Api-Key: ck_..."
    curl -s -X POST https://app.chat-card.ru/pos/purchases -H "X-Api-Key: ck_..." \
      -H "Content-Type: application/json" \
      -d '{"receipt_id":"test-1","card_code":"2200000000015","amount":100,"scanned_code":"LC2200000000015"}'
  4. Откройте mini-app по адресу из кабинета (https://app.chat-card.ru/o/<имя> через бота), оформите карту. В вашей системе должен прийти POST /register, а в приложении появиться баланс из GET /balance.

  5. Нажмите «Списать баллы» и отсканируйте QR кассой: POST /pos/redeem/consume должен вернуть номер карты. Второе сканирование вернёт 410.

  6. Включите в кабинете «Программа лояльности → История» и проверьте вкладку «История» (GET /history).

  7. Для дня рождения укажите у тестового клиента дату через days_before дней и выставьте send_hour меньше текущего часа. Планировщик проверяет сценарии раз в 15 минут и вызовет POST /bonus.

Чек-лист перед запуском

Возможность Часть B (реализуете вы) Часть A (вызываете вы)
Карта в приложении, регистрация, баланс /register, /balance, /welcome-bonus, /statuses —
Списание баллов по QR — /pos/redeem/consume
Статистика покупок, автовозврат — /pos/purchases
Уровни начисления, задержка, срок жизни применить accrual, отдавать pending и expiring в /balance /pos/loyalty-settings
Бонус за регистрацию правила в /register /pos/loyalty-settings
День рождения, автовозврат /bonus, birth_date в card и /balance /pos/purchases (для автовозврата)
Напоминание о сгорании /expiring —
История баллов /history —
Подарки за баллы списание баллов /pos/gifts/consume, /pos/gifts
Точки регистрации принять source_code в /register /pos/sources
Push из вашей системы — /pos/campaigns…
Тариф и доступные функции — /pos/plan
Поздравление с новым статусом /bonus (status_upgrade) card_status в /pos/purchases или /pos/card-status
Оценка после покупки /bonus (review) /pos/purchases, /pos/reviews
«Приведи друга» created в /register, /bonus (referral) /pos/purchases, /pos/referrals
RFM-сегменты — /pos/purchases, /pos/segments, audience.segment
Купоны партнёров /bonus/writeoff /pos/gifts
Вебхуки приёмник https://… с проверкой подписи —

Безопасность:

Вопросы по интеграции присылайте на info@chat-card.ru или через раздел «Поддержка» в кабинете.