openapi: 3.1.0 info: title: Чат-Карта — API, который реализует учётная система version: 1.1.0 description: | Эти методы реализует учётная система организации (1С или другая), а вызывает сервис «Чат-Карта». Адрес, логин и пароль указываются в кабинете («Подключение»). Только HTTPS, адрес должен быть виден из интернета. Сервис ждёт ответа 5 секунд. JSON в UTF-8. Подробно — docs/API.md. servers: - url: https://erp.example.ru/hs/loyalty description: Пример адреса вашей системы security: - Basic: [] paths: /register: post: operationId: registerClient summary: Найти клиента по телефону или выпустить карту description: | Идемпотентно по телефону. Здесь же начисляется бонус за регистрацию по разделу welcome из GET /pos/loyalty-settings: один раз на клиента и только за первое подключение (первый channel). requestBody: required: true content: application/json: schema: type: object required: [phone, channel, channel_user_id] properties: phone: { type: string, pattern: '^7\d{10}$' } first_name: { type: string, maxLength: 100 } last_name: { type: string, maxLength: 100 } channel: { type: string, enum: [max, telegram] } channel_user_id: { type: string } source_code: { type: string, description: Код точки регистрации } source_name: { type: string } birth_date: { type: string, format: date, description: 'Записать, только если у клиента дата пуста' } card_kind: { type: string, description: 'Вид карты из GET /statuses для новой карты; клиенту с картой вид не менять' } responses: '200': description: Карта клиента content: application/json: schema: type: object required: [card, created] properties: card: { $ref: '#/components/schemas/Card' } created: { type: boolean } welcome_bonus: oneOf: - type: 'null' - type: object properties: amount: { type: number } status: { type: string, enum: [accrued, pending] } /balance: get: operationId: getBalance summary: Баланс и состояние карты parameters: - { name: card, in: query, required: true, schema: { type: string } } responses: '200': description: Баланс content: application/json: schema: type: object required: [balance, available] properties: balance: { type: number } available: { type: number, description: Сколько можно потратить сейчас } status: { type: [string, 'null'] } active: { type: boolean } cashback_percent: { type: [number, 'null'] } cashback_total: { type: [number, 'null'] } birth_date: { type: [string, 'null'], format: date } expiring: oneOf: - type: 'null' - type: object properties: amount: { type: number } date: { type: string, format: date } pending: { type: number, description: Ещё не начисленные из-за задержки } '404': { description: Карта не найдена } /welcome-bonus: get: operationId: getWelcomeBonus summary: Условия бонуса за регистрацию (и проверка связи из кабинета) responses: '200': description: Условия content: application/json: schema: type: object properties: enabled: { type: boolean } amount: { type: number } existing_clients: { type: boolean } lifetime_days: { type: integer } /statuses: get: operationId: listStatuses summary: Статусы (уровни) карт responses: '200': description: Массив верхнего уровня content: application/json: schema: type: array items: type: object required: [name] properties: name: { type: string } cashback_percent: { type: [number, 'null'] } /bonus: post: operationId: accrueBonus summary: Начислить баллы по сценарию сервиса description: | Идемпотентно по key. Сервис повторяет вызов с тем же key, если ответ не получен. reason: birthday — день рождения, winback — возврат спящих, referral — «Приведи друга» (другу и пригласившему), review — баллы за отзыв на картах после оценки 5, status_upgrade — поздравление с новым статусом. requestBody: required: true content: application/json: schema: type: object required: [card, amount, key, reason] properties: card: { type: string } amount: { type: number, exclusiveMinimum: 0 } lifetime_days: { type: integer, minimum: 0, description: 0 — без срока } reason: { type: string, enum: [birthday, winback, referral, review, status_upgrade] } key: { type: string, maxLength: 200, example: birthday-2026-2200000000015 } comment: { type: string } responses: '200': description: Начислено (или уже было начислено с этим key) content: application/json: schema: type: object properties: accrued: { type: boolean } duplicate: { type: boolean } balance: { type: number } '404': { description: Карта не найдена } '409': { description: card_inactive — карта заблокирована } /bonus/writeoff: post: operationId: writeOffBonus summary: Списать баллы без кассы (купон партнёра за баллы) description: | Нужен для купонов партнёров (тариф «Сеть / PRO»): покупатель обменивает баллы на промокод в приложении. Идемпотентно по key: повтор с тем же key не списывает второй раз. Проверяйте остаток под блокировкой. Если метод не реализован (404), купон за баллы выдать нельзя; бесплатные купоны (points = 0) его не вызывают. requestBody: required: true content: application/json: schema: type: object required: [card, amount, key, reason] properties: card: { type: string } amount: { type: number, exclusiveMinimum: 0 } reason: { type: string, enum: [coupon] } key: { type: string, maxLength: 200, example: coupon-7-2200000000015-29342811 } comment: { type: string, example: 'Купон: Кофейня «Зерно» −20%' } responses: '200': description: Списано (или уже было списано с этим key) content: application/json: schema: type: object required: [written_off] properties: written_off: { type: boolean } duplicate: { type: boolean } balance: { type: number } '404': { description: Карта не найдена } '409': description: not_enough_points — баллов не хватает; card_inactive — карта заблокирована content: application/json: schema: type: object properties: error: { type: string, enum: [not_enough_points, card_inactive] } /expiring: get: operationId: listExpiring summary: Карты, у которых скоро сгорят баллы parameters: - { name: days, in: query, required: true, schema: { type: integer, minimum: 1, maximum: 365 } } responses: '200': description: Массив верхнего уровня, одна строка на карту — ближайшая дата сгорания content: application/json: schema: type: array items: type: object required: [card, amount, date] properties: card: { type: string } amount: { type: number } date: { type: string, format: date } /history: get: operationId: getHistory summary: История начислений и списаний по карте, новые сверху parameters: - { name: card, in: query, required: true, schema: { type: string } } - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } } responses: '200': description: Операции content: application/json: schema: type: object properties: items: type: array items: type: object required: [date, type, amount] properties: date: { type: string, example: '2026-10-19T09:12:00' } type: { type: string, enum: [accrual, redeem, burn, bonus, gift] } amount: { type: number, description: 'Плюс — начисление, минус — списание' } comment: { type: string } pending: { type: boolean } expires: { type: [string, 'null'], format: date } '404': { description: Карта не найдена } /card: get: operationId: findCardByPhone summary: Карта по телефону (зарезервировано, сейчас не вызывается) parameters: - { name: phone, in: query, required: true, schema: { type: string, pattern: '^7\d{10}$' } } responses: '200': description: Карта content: application/json: schema: type: object properties: card: { $ref: '#/components/schemas/Card' } '404': { description: Не найдена } components: securitySchemes: Basic: type: http scheme: basic schemas: Card: type: object required: [code] properties: code: { type: string, pattern: '^\w{1,40}$', description: Номер карты для штрих-кода и кассы } id: { type: string, description: ID карты в учётной системе } owner_name: { type: string } kind: { type: string, description: Название вида карты } status: { type: [string, 'null'], description: Совпадает с name из /statuses } birth_date: { type: [string, 'null'], format: date }