Онлайн-запись API
Онлайн-запись API — серверный доступ к записи клиентов APX Chat: филиалы, услуги, специалисты, свободное время и визиты. С ним можно записывать клиентов из своего сайта, приложения или бота и переносить визиты в учётную систему. API бесплатный: CRM, платная подписка, чат и каналы для него не нужны.
Базовый URL: https://api.apx.chat/api/v1/booking
Полный контракт — в OpenAPI JSON, раздел Booking. Интерактивная схема — в Swagger UI.
Авторизация
Токен выпускается в кабинете: Настройки → API-токены. Как выбрать scopes и срок действия — в описании API-токенов. Передавайте токен одним из двух заголовков:
X-Api-Token: tok_xxxxxxxxxxxxx
Authorization: Bearer tok_xxxxxxxxxxxxxОрганизация и приложение определяются по токену. Выдать токен с booking-scope может только
сотрудник, у которого есть такие же права на записи: для booking_read и booking_events_read
нужен просмотр всех визитов, для booking_write — создание, перенос, отмена и смена статуса всех
визитов, для каталога, настроек и контактов — свои отдельные права. Права только на свои визиты не
позволяют выдать доступ ко всей организации.
Scopes
| Scope | Доступ |
|---|---|
booking_read | Каталог, специалисты, свободное время, список и карточка записи, история |
booking_write | Создание, перенос, отмена, смена статуса, изменение ответов |
booking_catalog_write | Создание и изменение филиалов и услуг |
booking_settings_read | Чтение настроек |
booking_settings_write | Изменение настроек, формы и публикации |
booking_contacts_read | Контакты, ответы, заметки и настройки уведомлений клиента; нужен и для PATCH записи и фильтра clientUuid |
booking_events_read | Webhook-события записи; вместе с ним нужен booking_read |
Каталог, филиалы, услуги и специалисты доступны с любым одним из scopes booking_read,
booking_write, booking_catalog_write, booking_settings_write. Свободное время — с
booking_read или booking_write. Список, карточки и история визитов отдельно требуют
booking_read. В OpenAPI «любой из» описан расширением x-any-scopes, «все сразу» —
x-required-scopes.
booking_write не включает booking_contacts_read: отправить ответы клиента при создании можно,
а получить их обратно — только с отдельным scope. Без него в ответах нет clientUuid, answers,
formFields, formVersion, notes, notificationPreferences. Ссылку управления визитом и
секрет клиента API не отдаёт никогда.
Специалисты и графики
Специалист записи — это сотрудник организации. Его ведут в кабинете: в карточке сотрудника
(отметка «Принимает записи», услуги и филиалы), в составе сотрудников филиала и в «Кто оказывает»
услуги. Рабочее время берётся из графика сотрудника. Поэтому GET /specialists доступен только на
чтение.
Удалённые сотрудники не возвращаются в каталоге, справочниках и свободном времени, а их прошлые визиты остаются в списке записей. Сотрудника с будущими визитами удалить нельзя: сначала визиты переносят к другому специалисту или отменяют.
Формат ответов
Успешный ответ — конверт { "status": "OK", "data": ... }. Списки возвращают
{ "items": [], "nextCursor": null } внутри data, свободное время —
{ "slots": [], "nextCursor": null }. Создание отвечает 201, изменение — 200.
Методы
| Метод | Путь | Scope |
|---|---|---|
GET | /capabilities | booking_read |
GET | /settings | booking_settings_read |
PATCH | /settings | booking_settings_write |
GET | /catalog, /branches, /services, /specialists | любой booking-scope из списка выше |
POST, PATCH | /branches, /branches/{id} | booking_catalog_write |
POST, PATCH | /services, /services/{id} | booking_catalog_write |
GET | /availability | booking_read или booking_write |
GET | /appointments, /appointments/{id}, /appointments/{id}/history | booking_read |
POST | /appointments | booking_write |
PATCH | /appointments/{id} | booking_write и booking_contacts_read |
POST | /appointments/{id}/reschedule, /cancel, /status | booking_write |
Филиалы
Вид филиала — kind: place (по адресу, по умолчанию у нового филиала) или online (без
адреса). У филиалов, созданных раньше, поля нет — это place; в ответах kind есть всегда. Без
kind в PATCH вид филиала не меняется.
- У
placeадрес обязателен: создание без адреса, стирание адреса и переход изonlineвplaceбез адреса вернут400 BOOKING_VALIDATIONсdetails.fieldErrors.address. Старый филиал без адреса можно править (например, отправить в архив), не меняя адрес. - У
onlineнет адреса, города и координат: сервер сохраняетaddress: "", аcity,latitudeиlongitude—null; переданные значения не сохраняются. Безnameфилиал называется «Онлайн». Часовой пояс задаётся как у любого филиала. В записи клиент видит «Онлайн» вместо адреса и карты.
Город филиала (city) обязателен, если у активных филиалов организации больше одного города (в
записи клиент сначала выбирает город). Без города создание филиала, снятие города и возвращение
филиала из архива вернут 400 BOOKING_VALIDATION с details.fieldErrors.city — укажите город.
Старый филиал без города можно править, не задавая город. Для онлайн-филиала эта проверка не
действует.
Визит в ответах API v1 содержит branchKind: place | online — вид филиала визита на момент ответа.
В снимке визита он не хранится; филиал без поля kind — place. В webhook-событиях поля нет.
Кто оказывает услугу
Услуга содержит staffMode: all — все сотрудники филиалов услуги, selected — выбранные
специалисты. В режиме all связи специалист ↔ услуга (specialists[].services) сервер создаёт
сам, без переопределений цены и длительности: когда сохраняют активную услугу, состав сотрудников
филиала или профиль сотрудника с новым филиалом. Уже существующие связи и их цены не меняются.
Специалистов для selected выбирают только в кабинете.
Без staffMode новая услуга получает режим all, а PATCH режим не меняет. Услуги, созданные
раньше, работают в режиме selected. Каталог и свободное время, как и раньше, строятся по
specialists[].services.
Изменение поведения API v1: POST /services без staffMode теперь создаёт услугу в режиме all,
и сервер сам связывает её со специалистами её филиалов. Раньше связи вели только вручную, в
карточке сотрудника; вручную их ведут и в режиме selected. Чтобы новая услуга вела себя как
прежде, передавайте "staffMode": "selected". Услуги, созданные до этого изменения, остаются
selected.
Свободное время
GET /availability принимает branchId, serviceId, необязательный specialistId и локальные
даты филиала dateFrom, dateTo — не больше 31 дня включительно. Ответ — конкретные слоты в UTC с
часовым поясом филиала (IANA).
const url = "https://api.apx.chat/api/v1/booking/availability?branchId=BRANCH_ID&serviceId=SERVICE_ID&dateFrom=2026-10-13&dateTo=2026-10-19";
const response = await fetch(url, {
method: "GET",
headers: {
"X-Api-Token": "tok_xxxxxxxxxxxxx",
},
});
const data = await response.json();
console.log(data);Создание записи
Перед созданием прочитайте GET /catalog: в нём есть форма записи (form.version, поля и их
id) и согласие на обработку данных (privacy.version). Ответы передаются по id поля формы.
const url = "https://api.apx.chat/api/v1/booking/appointments";
const payload = {
"startsAt": "2026-10-13T07:00:00.000Z",
"branchId": "BRANCH_ID",
"serviceId": "SERVICE_ID",
"specialistId": "SPECIALIST_ID",
"formVersion": 3,
"answers": {
"FIELD_ID_NAME": "Анна",
"FIELD_ID_PHONE": "+79990000000"
},
"notificationPreferences": {
"email": false,
"messenger": false
},
"privacyConsent": {
"accepted": true,
"version": 1
}
};
const response = await fetch(url, {
method: "POST",
headers: {
"X-Api-Token": "tok_xxxxxxxxxxxxx",
"Idempotency-Key": "create-2026-10-13-0001",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await response.json();
console.log(data);Если слот уже заняли, придёт 409 BOOKING_SLOT_TAKEN — обновите свободное время и выберите другой.
Если форму успели изменить — 409 BOOKING_FORM_CHANGED: перечитайте её и подтвердите ответы.
specialistId при создании необязателен. Без него специалиста назначает сервер: из специалистов,
у кого выбранное время свободно, — тот, у кого меньше записей в этот день (по локальной дате
филиала записи; считаются визиты во всех филиалах специалиста и «Пришёл без записи», отменённые не
считаются), при равенстве — тот, кому дольше всего не назначали. Назначенный специалист приходит в
specialistId ответа. Перенос (/reschedule) всегда требует specialistId.
При записи к любому специалисту (specialistId не передан), если слот не подошёл никому, ответ —
по самой сильной причине: занят хотя бы у одного — 409 BOOKING_SLOT_TAKEN; иначе пересекающиеся
смены — 409 BOOKING_SCHEDULE_CONFLICT; 422 BOOKING_OUTSIDE_AVAILABILITY — только если время
вне доступности у всех. В каждом случае обновите свободное время и выберите другой слот.
Идемпотентность и версии
Все изменяющие запросы требуют заголовок Idempotency-Key: 8–128 печатных ASCII-символов без
пробелов. При повторе той же операции отправляйте тот же ключ. Тот же ключ с другим содержимым
вернёт 409 BOOKING_IDEMPOTENCY_CONFLICT. Ключ действует в пределах организации, приложения,
операции и объекта; ротация токена его не сбрасывает. Повтор не обходит отзыв scope, токена или
прав сотрудника и заново применяет текущие правила скрытия контактов.
Изменение существующего объекта требует expectedVersion. При расхождении придёт
409 BOOKING_VERSION_CONFLICT, а details.currentVersion подскажет, что объект нужно перечитать.
PATCH /appointments/{id}меняет ответы, заметки и настройки уведомлений.POST /appointments/{id}/rescheduleменяет время, филиал и специалиста;specialistIdобязателен. Услугу визита изменить нельзя.POST /appointments/{id}/cancelотменяет визит. Значениеcancelledв/statusне принимается.
Список записей
GET /appointments принимает время в UTC: from и until — полуоткрытый интервал не длиннее
31 суток. Фильтры: branchId, specialistId, status, clientUuid; страницы — cursor и
limit (1–200, по умолчанию 100). Курсор привязан к организации и фильтрам. clientUuid — только
действующий идентификатор клиента этой организации; введённые клиентом email или телефон клиента
не связывают.
Уведомления по номеру и ответственные филиала
Настройки (GET/PATCH /settings) содержат notifications.phoneMessenger:
{ enabled, maxChannelUuid, telegramChannelUuid, templates: { confirmation, reminder, rescheduled, cancelled } }.
Каналы — подключённые личные аккаунты MAX и Telegram организации; включить настройку без каналов
нельзя. Шаблоны — до 1000 символов с подстановками {услуга}, {дата}, {время}, {специалист},
{филиал}, {адрес}, {ссылка}; null в PATCH возвращает текст по умолчанию. У онлайн-филиала
уведомления клиенту (письмо, MAX, Telegram, подстановка {адрес}) и заметки команде пишут «Онлайн»
вместо адреса; пустые подстановки не оставляют висящих «, ».
Сообщения уходят, только если в записи передано согласие notificationPreferences.phoneMessenger: true
и в ответах формы есть телефон. Сначала MAX, затем Telegram, затем письмо (если email указан и не
выбран отдельно); следующие сообщения идут туда же, куда дошло первое.
Ответ клиента «1» отмечает визит подтверждённым — событие истории customer_confirmed, версия визита
не меняется; «2» отменяет визит, если ещё не прошёл customerCutoffMinutes. Ответом считается только
первое входящее сразу после уведомления: последнее исходящее диалога — уведомление, диалог закрыт и
у клиента в канале нет другого активного диалога, визит закончился не раньше чем сутки назад. Иначе
это обычное входящее. Ответ цитатой (reply-to) на уведомление относится к визиту этого уведомления,
без цитаты — к визиту последнего уведомления. «2» без цитаты при нескольких действующих визитах
ничего не отменяет: диалог уходит операторам с заметкой обо всех визитах. Отмена ответом — как
отмена клиентом по ссылке управления (история cancelled, источник public).
Филиал (/branches) может содержать responsible — кто отвечает клиентам, которые ответили на
уведомление свободным текстом: { "mode": "employees", "userUuids": [] } (1–50 сотрудников
организации) или { "mode": "department", "departmentUuid": "…" }; null — общая очередь канала.
Архив филиала очищает поле.
Ошибки
Ошибка — { status, code, message, details?, timestamp, path }. Сообщения безопасные и на
русском; неизвестные поля в запросе запрещены. details.fieldErrors подсказывает, какие поля
формы исправить.
| Код | HTTP | Что делать |
|---|---|---|
BOOKING_VALIDATION | 400 | Исправить поля |
BOOKING_FORBIDDEN | 403 | Проверить scopes токена и права сотрудника |
BOOKING_NOT_FOUND | 404 | Объект отсутствует или относится к другой организации |
BOOKING_SLOT_TAKEN | 409 | Обновить свободное время и выбрать слот |
BOOKING_VERSION_CONFLICT | 409 | Перечитать объект |
BOOKING_FORM_CHANGED | 409 | Перечитать форму и подтвердить ответы |
BOOKING_IDEMPOTENCY_CONFLICT | 409 | Не использовать ключ повторно для другого содержимого |
BOOKING_INVALID_STATUS | 409 | Перечитать текущий статус |
BOOKING_SCHEDULE_CONFLICT | 409 | Время визита попадает на пересекающиеся смены специалиста — выберите другое время; при архиве филиала (details.reason: future_appointment) — сначала перенесите или отмените его будущие визиты |
BOOKING_OUTSIDE_AVAILABILITY | 422 | Выбрать допустимое время |
BOOKING_CUTOFF | 422 | Перенос или отмена клиентом закрыты правилами записи |
BOOKING_RATE_LIMITED | 429 | Подождать Retry-After секунд |
Webhooks
Пять событий: booking.appointment.created, booking.appointment.updated,
booking.appointment.rescheduled, booking.appointment.cancelled,
booking.appointment.status_changed. Подписка настраивается в кабинете в разделе
Настройки → Вебхуки: у выбранного API-приложения должны быть
одновременно booking_read и booking_events_read.
Тело события — { id, type, apiVersion, occurredAt, data }, где data содержит только
appointmentId, version, branchId, serviceId, specialistId, startsAt, endsAt,
status. Контактов, ответов, ссылок и токенов в событиях нет. Подпись — заголовок
X-APX-Signature: v1= и hex HMAC-SHA256 от timestamp + "." + тело запроса с секретом подписки;
время — X-APX-Event-Timestamp, ID события — X-APX-Event-Id (при повторной доставке не меняется).
Подробнее о проверке подписи — в разделе Webhook.
Квитанции посетителя сайта
Клиент, который записался через виджет сайта, видит свои визиты в разделе «Ваши записи» и получает
ссылку управления визитом. Эти данные виджет восстанавливает сам по подписанному токену посетителя
(X-Client-Token) через отдельные маршруты посетителя: /api/public/booking/visitor-receipts и
/api/public/booking/appointments/{id}/visitor-receipt. Они не входят в API v1, не принимают API-токен или токен сотрудника и не выдают секрет управления ни персоналу, ни
интеграциям. Вызывать их из своей интеграции не нужно.
Не копируйте ссылку управления визитом в переписку, уведомления операторам, экспортируемые переменные и логи: по ней клиент переносит и отменяет визит без входа.
Публичная страница и виджет
Запись без интеграции доступна клиентам по ссылке /book/{ключ записи} и во вкладке «Запись»
виджета сайта. Название и описание страницы меняются через PATCH /settings (publicPage.title —
до 80 символов, publicPage.description — до 500 символов простого текста), логотип загружается
только в кабинете. Открыть запись своей кнопкой на сайте с виджетом можно методом
booking.open().
Настройка specialistSelection (client — по умолчанию, или auto; меняется через
PATCH /settings с expectedVersion) определяет, показывают ли клиенту выбор специалиста виджет,
страница /book и бот. Публичный каталог отдаёт её в policy.specialistSelection. При auto эти
интерфейсы показывают свободное время всех подходящих специалистов и создают запись без
specialistId — специалиста назначает сервер по правилу из раздела «Создание записи». На API v1
настройка не влияет: передавать specialistId или нет, решает ваше приложение.