Подключение внешней системы (API)


Что это и зачем

Внешняя система — касса, учётная или гостиничная программа — может забирать меню, читать заказы и счета заведения и передавать нам заказ, который приняла сама. Для этого есть отдельный API: он работает от имени заведения и не требует учётной записи сотрудника.

Адрес — это адрес приложения Qreaty плюс /api/external/v1. В рабочей версии это https://qreaty.com/api/external/v1; готовый адрес показан в разделе «Доступ по API» (как его открыть) — его можно скопировать оттуда. Все запросы ниже даны относительно этого адреса: GET /menu/items значит GET https://qreaty.com/api/external/v1/menu/items.

Ответы — JSON в формате JSON:API (data, attributes), запросы с телом — JSON. Токен передаётся в заголовке Authorization: Bearer <токен>, а Accept: application/json желателен всегда.

Язык. Названия и описания приходят на языке, который вы укажете: параметр ?locale=vi (код языка: ru, en, vi…) или заголовок Accept-Language: vi. Какие языки есть, API говорит само: в каждом ответе GET /menu/items и GET /menu/modifiers есть meta.menus — по каждому меню список locales (на каких языках оно переведено) и source_locale — язык оригинала, на котором владелец вводил меню. Если на запрошенный язык перевода нет (или язык не указан), приходит оригинал.

{primary} Токен принадлежит заведению, а не человеку: уйдёт сотрудник, который его выпустил, — токен продолжит работать, пока вы его не отзовёте. Каждый токен видит только своё заведение.

Что умеет API:

Что Запрос Нужное право токена
Все блюда GET /menu/items menu:read
Группы модификаторов и опции GET /menu/modifiers menu:read
Столы (id, number, name, zone) GET /tables orders:read
Заказы GET /orders, GET /orders/{id} orders:read
Счета GET /bills, GET /bills/{id} orders:read
Принять заказ POST /orders orders:write

Токен доступа

Токен выпускает владелец или тот, кто может менять настройки заведения.

  1. Откройте Настройки → Учётная система (POS) и найдите раздел «Доступ по API».
  2. Нажмите «Выпустить токен», назовите его по системе («Касса, ресепшен») и отметьте, что ему разрешено: читать меню, читать заказы и счета, создавать заказы. Дайте ровно столько, сколько нужно: система, которая только сверяет прайс, заказов создавать не должна.
  3. При желании задайте срок действия.
  4. Скопируйте токен сразу. Он показывается один раз; потом в списке видно только имя, права и время последнего использования.

{warning} Токен — как пароль: любой, у кого он есть, действует от имени заведения в пределах выданных прав. Не отправляйте его в чатах и не храните в репозитории. Если токен мог утечь — отзовите его в том же разделе и выпустите новый: отозванный перестаёт работать сразу.

Один токен — одна подключённая система. Так видно, кто и когда обращался, и отзыв одной системы не ломает остальные.

SKU: код блюда и опции

Внешняя система называет позиции SKU — кодом, который у неё записан в справочнике. У каждого блюда и у каждой опции модификатора есть SKU, пустым он не бывает:

  • Новой позиции мы сами присваиваем код QR-D<номер> (блюдо) или QR-M<номер> (опция) — он уникален и не меняется.
  • Чтобы совпасть со справочником партнёра, впишите его код: откройте блюдо (или опцию в модификаторе) и измените поле «SKU». Допустимы буквы, цифры и знаки . _ - /.
  • В пределах одного меню SKU не повторяется — программа не даст сохранить дубль. Одно и то же блюдо в двух меню заведения может иметь один код.
  • «Копия» блюда получает свой новый код, а копия меню целиком сохраняет коды как есть.

В заказе SKU запоминается вместе с названием: если вы смените код блюда завтра, вчерашние счета останутся с тем кодом, под которым блюдо продали.

{primary} Если у двух блюд одного меню совпал код (так бывает у старых данных, привязанных вручную), API скажет об этом: в ответе GET /menu/items и /menu/modifiers есть meta.duplicate_skus. Пока дубль не исправлен, заказать такой SKU нельзя — запрос получит ошибку.

Меню: блюда и модификаторы

GET /menu/items возвращает блюда активного меню — того, по которому заказывают гости и касса, — по 50 за страницу (?per_page= до 200, ?page=). Другое меню можно запросить явно: filter[menu]=<id>; номера меню и их языки есть в meta.menus.

Ключ блюда — его SKU: он же стоит в id и именно его присылают обратно в заказе. У блюда: sku, name, description, price_type (fixed — фиксированная, from — «от», range — диапазон, variable — по запросу) с price_value / price_min / price_max / price_unit, currency, kind (food, drink, alcohol, other), section, available — можно ли заказать прямо сейчас (не скрыто, не в стоп-листе, секция включена), sold_out — стоит ли оно в стоп-листе и modifier_group_ids — какие группы модификаторов к нему привязаны.

Фильтры: filter[available]=1, filter[section], filter[sku], filter[menu], filter[updated_since]=2026-10-14T00:00:00Z — удобно для регулярной сверки: берёте только то, что менялось.

GET /menu/modifiers — группы модификаторов активного меню с опциями. Опция называется своим SKU (options[].sku) и несёт name, price, is_default, max_qty и prices — цена по размеру (driver_option_sku — опция-размер, price — цена при ней). У группы: name, pricing_mode (replace — цена опции заменяет цену блюда, как размер; add — добавляется, как топпинг), required, selection_min / selection_max — сколько опций можно выбрать, item_skus — к каким блюдам группа привязана, parent_option_sku — если группа открывается под опцией другой группы. id группы — её ссылка в modifier_group_ids блюда.

curl -s -g "https://qreaty.com/api/external/v1/menu/items?filter[available]=1&locale=vi" \
  -H "Authorization: Bearer $TOKEN" -H "Accept-Language: vi"

Готовые запросы: меню

{primary} Все запросы ниже собраны в готовую коллекцию для Postman: скачать. Импортируйте файл, впишите токен в переменную коллекции token — и можно отправлять; SKU и номер стола подставятся сами.

Адрес с фильтрами собирается из частей: путь?filter[имя]=значение&filter[имя2]=значение2. Ниже — готовые строки, которые можно подставлять как есть. В curl добавляйте -g, чтобы квадратные скобки не трактовались как шаблон; в Postman их вводить так же, как здесь. Знак + во времени пишется как %2B (+07:00 → %2B07:00), иначе он превратится в пробел.

Что нужно Запрос
Все блюда на вьетнамском, по 200 на страницу GET /menu/items?locale=vi&per_page=200
Только блюда, которые сейчас можно заказать GET /menu/items?filter[available]=1
Блюдо по его SKU GET /menu/items?filter[sku]=PHO-1
Блюда одного меню (id меню — в meta.menus) GET /menu/items?filter[menu]=12
Что изменилось в меню с указанного момента GET /menu/items?filter[updated_since]=2026-10-14T00:00:00Z
Группы модификаторов одного меню GET /menu/modifiers?filter[menu]=12

Заказы и счета

GET /orders — заказы (раунды счёта), новые сверху. Каждый — со строками, выбранными опциями, статусом, столом, номером и временем. Строка называет блюдо его SKU (lines[].sku), а вложенные опции — lines[].modifiers[].sku.

Фильтр Что делает
filter[status] pending, confirmed, completed, rejected, cancelled; несколько — через запятую
filter[source] guest (гость по QR), staff (официант), external (принят по API)
filter[table] номер стола
filter[bill] id счёта
filter[external_id] id заказа в вашей системе
filter[placed_from], filter[placed_to] интервал времени приёма (от — включительно, до — нет)
filter[updated_since] всё, что менялось с указанного момента

GET /bills — счета столов: сумма, налог, способ оплаты (tender), номер комнаты, когда открыт и закрыт, и список id заказов. С ?embed=orders заказы приходят целиком. Фильтры: filter[status]=open|closed, filter[tender] (cash, transfer, card, ewallet, room_charge, voucher, none — без записанной оплаты), filter[table], filter[number], filter[room_number], filter[opened_from|opened_to], filter[closed_from|closed_to], filter[updated_since]. Сортировка: ?sort=-closed_at.

Время в запросах — ISO 8601 (2026-10-14T09:00:00+07:00); в ответах — тоже.

{primary} Чтобы не терять изменения, храните момент последней успешной выборки и в следующий раз просите filter[updated_since] с небольшим запасом назад — повторные записи вы всё равно узнаете по id.

Готовые запросы: заказы и счета

Те же правила (-g в curl, %2B вместо +). Время — по вьетнамскому поясу (+07:00).

Что нужно Запрос
Заказы, ждущие подтверждения GET /orders?filter[status]=pending
Подтверждённые и завершённые GET /orders?filter[status]=confirmed,completed
Заказы за 14 октября GET /orders?filter[placed_from]=2026-10-14T00:00:00%2B07:00&filter[placed_to]=2026-10-15T00:00:00%2B07:00
Только пришедшие по API GET /orders?filter[source]=external
Подтверждённые заказы стола 5 GET /orders?filter[table]=6512&filter[status]=confirmed
Всё, что менялось после последней сверки, от старых к новым GET /orders?filter[updated_since]=2026-10-14T09:00:00Z&sort=updated_at
Наш заказ по номеру в вашей системе GET /orders?filter[external_id]=POS-100234
Открытые счета GET /bills?filter[status]=open
Закрытые счета за 14 октября, с заказами и строками внутри GET /bills?filter[status]=closed&filter[closed_from]=2026-10-14T00:00:00%2B07:00&filter[closed_to]=2026-10-15T00:00:00%2B07:00&embed=orders
Счета, записанные на номер отеля GET /bills?filter[status]=closed&filter[tender]=room_charge
Счета конкретной комнаты GET /bills?filter[room_number]=412
Закрытые счета без записанной оплаты GET /bills?filter[status]=closed&filter[tender]=none
Следующая страница добавьте &page=2 к любому из запросов

Приём заказа от внешней системы

POST /orders — внешняя система передаёт заказ, который приняла у гостя. Он попадает в заведение как обычный подтверждённый заказ: уходит на кухню и в печать, списывает склад, попадает в счёт стола и аналитику. Цену считаем мы по меню — присылать цены не нужно.

{
  "external_id": "POS-100234",
  "table": 6512,
  "room_number": "412",
  "note": "Без кинзы",
  "items": [
    {
      "sku": "PHO-1",
      "quantity": 2,
      "note": "Острое отдельно",
      "modifiers": [
        { "sku": "SIZE-L" },
        { "sku": "EGG", "quantity": 1 }
      ]
    }
  ]
}
Поле Что значит
external_id Номер заказа в вашей системе. Обязателен: по нему повтор запроса распознаётся
table id стола ровно так, как его вернул GET /tables (число вроде 6512; номер стола на табличке — это поле number, оно нужно только чтобы сопоставить ваши столы с нашими)
room_number Номер комнаты, чтобы записать заказ на номер отеля
served true — заказ уже подан: записывается как проведённый, на кухню не идёт
items[].sku, quantity Блюдо и количество (1–99)
items[].modifiers[] Выбранные опции по SKU; вложенные — в их modifiers

Ответ 201 содержит созданный заказ — с нашим id, номером и ценами.

Повтор безопасен. Если связь оборвалась и вы отправили тот же external_id с тем же содержимым, придёт 200 и уже созданный заказ — второго не появится. Тот же external_id с другим содержимым — ошибка IDEMPOTENCY_KEY_REUSED: это либо баг, либо новый заказ под старым номером.

Заказ принимается только на блюда активного меню, которые сейчас можно заказать (не скрыты, не в стоп-листе).

Проверка в Postman

Готовая коллекция Postman содержит все запросы этой страницы с включаемыми фильтрами и тестами. Скачать: qreaty-external-api.postman_collection.json.

  1. В Postman нажмите Import (слева вверху) и перетащите скачанный файл в окно — либо вставьте в поле ссылку на файл (https://qreaty.com/downloads/qreaty-external-api.postman_collection.json) и нажмите Enter. Появится коллекция «Qreaty — External API».
  2. Откройте коллекцию и вкладку Variables. В token вставьте токен из раздела «Доступ по API» (как выпустить). base_url уже указывает на https://qreaty.com/api/external/v1; для другого окружения замените его (без слэша в конце). Нажмите Save.
  3. Отправьте Menu → Menu items, Menu modifiers и Tables → List tables: тесты сами сохранят sku, option_sku и номер стола в служебные переменные коллекции (они появятся в списке после первого запуска).
  4. Отправьте Orders → Place order. Ответ 201 — заказ создан; повторная отправка с тем же external_id (создайте переменную коллекции keep_external_id с любым значением) вернёт 200 и тот же заказ.
  5. Фильтры в запросах «List orders» и «List bills» выключены галочками — включайте нужные и подставляйте свои значения.

{primary} Авторизация — Bearer-токен на всю коллекцию, отдельно в запросах её настраивать не нужно. Если в ответах 401 — проверьте, что токен вставлен без пробелов и не отозван; 403 — токену не хватает права для этого запроса.

Ошибки и пределы

Код Когда Что делать
401 нет токена, он отозван, истёк или это не токен заведения выпустить новый токен
403 токену не хватает права для этого запроса выдать право или выпустить другой токен
404 заказа или счёта с таким id в этом заведении нет проверить id
422 неверное тело: неизвестный SKU или стол, блюдо недоступно сейчас, не выбрана обязательная опция прочитать errors — у каждой ошибки указан путь, например items.0.sku
422 + IDEMPOTENCY_KEY_REUSED external_id уже использован с другим содержимым сменить номер или отправить прежнее содержимое
429 слишком часто повторить через Retry-After секунд

Предел — 120 запросов в минуту на токен. Для регулярной сверки меню этого с запасом: берите filter[updated_since] и страницы по 200.

Машиночитаемую спецификацию (OpenAPI) для разработчика интегратора можно запросить в поддержке.