API YONA для бизнеса

Салон может подключить к YONA свою программу — 1С, собственную CRM, бота или сайт. Есть две стороны: вы спрашиваете нас (REST-запросы) и мы сами стучимся к вам, когда что-то произошло (вебхуки). Спрашивать каждую минуту не нужно и не стоит: подписка на события бесплатна и не расходует предел запросов.

Кому доступно

API и вебхуки входят в тарифы PRO и ENTERPRISE. На пробном периоде доступна песочница: ключ выпускается, запросы работают с низким пределом, тестовое событие уходит по кнопке, но настоящие записи вебхуками не рассылаются. На тарифе SOLO API не работает.

Ключ

Ключ выпускает владелец салона в разделе «Интеграции». Ключ показывается один раз — мы храним только его отпечаток и восстановить его не сможем. У ключа есть права: выдавайте ровно те, что нужны программе. Отозванный ключ перестаёт работать со следующего запроса.

Как обращаться

Все маршруты начинаются с /api/v1. Ключ передаётся заголовком Authorization. Проверить ключ — GET /api/v1/me: вернётся ваш салон, права ключа и предел запросов.

curl -H "Authorization: Bearer yona_ab12cd34_…" \ https://yona.uz/api/v1/services

Пределы

PRO — 120 запросов в минуту на ключ, ENTERPRISE — 600, пробный период — 20. Превышение отвечает 429 и заголовком Retry-After. Счётчика оплаченных вызовов нет: мы не берём денег за каждый запрос.

Маршруты

HTTPПравоЧто делает
GET /api/v1/mecatalog:readСалон, права ключа и предел запросов
GET /api/v1/servicescatalog:readУслуги с ценой и длительностью (?archived=1 — вместе с архивными)
GET /api/v1/masterscatalog:readСотрудники и кто из них принимает клиентов
GET /api/v1/slots?serviceId&date&masterIdschedule:readСвободное время на день салона (YYYY-MM-DD)
GET /api/v1/bookings?from&to&status&masterId&customerId&limit&cursorbookings:readЗаписи страницами
GET /api/v1/bookings/{id}bookings:readОдна запись
POST /api/v1/bookingsbookings:writeСоздать запись: serviceId, startTime и либо customerId, либо customerName с customerPhone
POST /api/v1/bookings/{id}/cancelbookings:writeОтменить запись
GET /api/v1/customers?limit&cursorcustomers:readКлиенты салона страницами

Вебхуки

Адрес добавляет владелец салона там же, в «Интеграциях». Мы шлём POST с телом JSON. Отвечайте кодом 2xx как можно быстрее: тело ответа мы не читаем, а работу лучше отложить в свою очередь. Адрес должен быть https и доступен из интернета — во внутреннюю сеть мы не ходим и по перенаправлениям не идём.

События

  • booking.created — Новая запись
  • booking.confirmed — Запись подтверждена
  • booking.changed — Запись перенесена
  • booking.cancelled — Запись отменена
  • booking.completed — Визит завершён

Подпись

Каждая отправка подписана вашим секретом. В заголовке X-Yona-Signature приходит t=<время>,v1=<подпись>, где подпись — HMAC-SHA256 по строке «время.тело». Проверяйте её и отвергайте всё, что старше пяти минут: без этого запись можно повторить чужими руками.

Не проверять подпись — значит принимать записи от кого угодно, кто узнал ваш адрес.

const signature = request.headers['x-yona-signature']; // t=1758297600,v1=… const [, timestamp, given] = /^t=(\d+),v1=([0-9a-f]+)$/.exec(signature) ?? []; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return reject(); const expected = crypto .createHmac('sha256', YOUR_WEBHOOK_SECRET) .update(timestamp + '.' + rawBody) // the raw body, before JSON.parse .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(given, 'hex'))) return reject();

Повторы

Если вы ответили не 2xx или не ответили вовсе, мы повторим до шести раз с растущей паузой. Одно и то же событие может прийти дважды — сверяйте заголовок X-Yona-Delivery и обрабатывайте повтор как уже сделанное. Адрес, который отказывает пятнадцать доставок подряд, мы отключаем и пишем об этом в журнал на экране.

Отказы

Ответ с ошибкой — это JSON с полями error и code. Ветвитесь по code: текст в error написан для человека, читающего лог, и может измениться.

Если что-то не так

Журнал отправок на экране «Интеграции» показывает каждую доставку: время, событие, ответ вашего сервера и кнопку «Повторить». С него и начинайте.