Интеграции для владельцев: меню, столы, плагины маркетплейса, usage-метрики.
База: https://api.moonlauncher.org/api/public-api/ · Доки: https://docs.moonlauncher.org · Формат: JSON, заголовок X-Api-Key
1. В кабинете владельца откройте «API ключи», создайте ключ и сразу скопируйте секрет — больше он не покажется.
curl https://api.moonlauncher.org/api/public-api/menu/my-cafe/ curl -H "X-Api-Key: wsk_..." \ "https://api.moonlauncher.org/api/public-api/owner/restaurants/"
2. Ключ с scope OWNER видит все точки владельца; для действий с точкой передавайте ?restaurant_id=.
| Credential | Формат | Что это |
|---|---|---|
X-Api-Key | wsk_... | Новый секрет. В БД хранится только SHA-256, показывается 1 раз при создании/ротации |
X-Api-Key | wl_... | Legacy-ключ (обратная совместимость) |
| — | wpk_... | Публичный ID, не credential. Светить можно, вызывать API им нельзя (401) |
Управление ключами (создание, ротация, отзыв) — только по JWT владельца в кабинете: /api/public-api/keys/. По ApiKey управлять ключами нельзя.
| Scope | Контур |
|---|---|
RESTAURANT | Одна точка. Без явных permissions — read-only (owner.read, plugins.read, menu.read) |
OWNER | Все точки владельца и его сети. 1 владелец = 1 API-контур |
CHAIN | Одна сеть (все её точки) |
Тонкие права — permissions: owner.read owner.write plugins.read plugins.write menu.read orders.read orders.write reports.read keys.read. Пусто = legacy full.
Чужое недоступно по построению: restaurant_id вне контура даёт 403. Если ключ покрывает много точек и restaurant_id не передан — 400 со списком точек.
| Метод | Путь | Ответ |
|---|---|---|
| GET | /api/public-api/menu/<slug>/ | Категории + блюда: id, name, description, price, image, cooking_time |
| GET | /api/public-api/tables/<slug>/ | Столы: id, number, is_occupied |
| Метод | Путь | Право | Ответ |
|---|---|---|---|
| GET | /api/public-api/owner/restaurants/ | owner.read | id, name, slug, chain_id всех точек контура |
| GET | /api/public-api/owner/plugins/<slug>/status/?restaurant_id= | plugins.read | enabled, status, egress плагина в точке |
| POST | /api/public-api/owner/plugins/<slug>/action/ | plugins.write | {"restaurant_id","action","data"} → результат action. Спец-действия: approve_host revoke_host fetch |
| GET | /api/public-api/owner/usage/ | keys.read | today_requests, quota_left, errors_today, avg_ms, top_endpoints, abuse_score, blocked_until |
| GET | /api/public-api/owner/menu/ | menu.read | Полное меню: категории, блюда (без cost_price), модификаторы, стоп-лист |
| GET | /api/public-api/owner/orders/?status=&limit= | orders.read | Чеки: номер, статус, стол, сумма, официант. limit до 200 |
| GET | /api/public-api/owner/orders/<id>/ | orders.read | Чек с позициями (блюдо, qty, цена, notes, гость, модификаторы) |
| GET | /api/public-api/owner/tables/live/ | owner.read | Столы: занятость, активный чек, активная сессия |
| GET | /api/public-api/owner/bookings/?date_from=&status= | owner.read | Брони (по умолчанию предстоящие) |
| GET | /api/public-api/owner/reports/summary/?days= | reports.read | Выручка, чеки, средний чек, по статусам (days 1–90) |
| GET | /api/public-api/owner/inventory/ | inventory.read | Остатки: ингредиент, ед., qty, минимум, флаг is_low |
| GET | /api/public-api/owner/reviews/ | owner.read | Оценки гостей с комментариями |
| GET | /api/public-api/owner/staff/ | staff.read | Сотрудники: только имя и роль (без PIN/телефонов) |
| POST | /api/public-api/owner/orders/create/ | orders.write | {"restaurant_id","table_id|table_number","items":[{"dish_id","qty"}]} + idempotency по client_request_id. Стол занят → 409 |
| POST | /api/public-api/owner/orders/<id>/add-items/ | orders.write | Дозаказ в активный чек |
| POST | /api/public-api/owner/orders/<id>/status/ | orders.write | Кухонная цепочка по ALLOWED_TRANSITIONS. PAID/AWAITING_PAYMENT → только с money opt-in (раздел 8) |
| POST | /api/public-api/owner/bookings/create/ | bookings.write | Бронь с проверкой вместимости и пересечений → 409 при конфликте |
| POST | /api/public-api/owner/bookings/<id>/cancel/ | bookings.write | Отмена активной брони |
{
"restaurant_id": "3fa85f64-...",
"action": "details",
"data": { "order_id": "3fa85f64-..." }
}
Generic webhooks выключены: POST /api/public-api/webhook/<provider>/ всегда отвечает 410.
Модуль должен быть установлен и включён в точке, иначе 404 not installed / 400 module disabled. Неизвестный action — 400 unknown action.
| Slug | Actions | Что делает |
|---|---|---|
offline-pos | sync_batch | Пачка до 200 офлайн-чеков, идемпотентность по client_request_id |
fraud-radar | details | Антифрод: репринты, дозаказы после счёта, paid-cancelled |
guest-check | assign_guest, merge_orders | Счёт по гостям, склейка дозаказов |
discount-guard | dry_run | Честный просчёт корзины без двойных скидок |
staff-rating | accrue_bonus | Рейтинг официантов, начисление бонуса |
cost-trend | — (только status) | Динамика цен поставок, задетые техкарты |
gift-cards | issue, redeem, set_mode | Подарочные карты |
chain-sync | push_menu, push_promos | Копирование меню/акций между точками сети |
courier-desk | assign | Назначение курьера на доставку, SLA |
receipt-designer | save_template, preview | Шаблон чека, превью по живому заказу |
scales-barcodes | lookup, set_barcode, price_tags, weight_price | Штрихкоды весового товара |
course-service | set_course, order_courses | Курсы подачи блюд |
feedback-push | notify | Пуш в TG о низкой оценке гостя |
booking-report | remind_list | Брони, no-show, список напоминаний |
Дефолт: 60 req/min на ключ (скользящее окно), daily_quota: 0 = без лимита. Каждый запрос пишется в лог (endpoint, method, status, ms, IP).
| Код | Когда |
|---|---|
| 401 | Нет X-Api-Key, неверный/протухший ключ, передан wpk_ вместо секрета |
| 403 | scope denied (нет права), IP not allowed, точка вне контура, временный блок за абьюз |
| 400 | Нужен restaurant_id (ключ покрывает много точек), плохой action/data |
| 404 | Нет плагина/модуля/заказа |
| 429 | rate limit exceeded / daily quota exceeded |
| 409 | TABLE_HAS_ACTIVE_ORDER (второй активный чек на стол — откройте существующий), DISH_ON_STOP_LIST, BOOKING_CONFLICT (слот пересекается) |
| 403 | MONEY_OPS_EXCLUDED — PAID/AWAITING_PAYMENT через паблик ставить нельзя |
| 400 | ILLEGAL_TRANSITION — статус вне ALLOWED_TRANSITIONS |
Антиабьюз: серия из 10 ошибок подряд — блок 15 минут, серия из 5 рейтлимитов — блок 1 час. Успешный запрос сбрасывает счётчик. Метрики — в owner/usage/.
Только по JWT владельца (Authorization: Bearer ...), endpoints /api/public-api/keys/:
| Вызов | Эффект |
|---|---|
POST /keys/ | Создать. Секрет wsk_... в ответе один раз, дальше только wpk_... |
POST /keys/<id>/rotate/ | Новый секрет, public_id стабилен, блокировки сняты |
POST /keys/<id>/revoke/ | Kill-switch: мгновенно гасит ключ |
GET /keys/<id>/usage/ | today_requests, quota_left, errors_today, avg_ms, top_endpoints |
Опции ключа: expires_at, ip_allowlist (пусто = любые IP), rate_limit_per_minute, daily_quota. Утечка/подозрение — сразу revoke + rotate.
Скоупы payments.read (журнал операций) и payments.write (касса) — опасные: выключены всегда, даже у старых full-ключей. Без них денежные вызовы отвечают 403 DANGEROUS_SCOPE_DISABLED.
1. POST /api/public-api/keys/<id>/dangerous/request/ {"scope"} — вернёт предупреждение «Вы уверены, что хотите включить … для посторонних API-сервисов?», текст условий и слово-подтверждение.
2. POST /api/public-api/keys/<id>/dangerous/confirm/ {"scope","confirm_text":"Accept","legal_accepted":true,"legal_version":"..."} — включает. Кто/когда/IP/версия условий пишутся в аудит. Выключить в любой момент: POST .../dangerous/revoke/ — доступ гаснет мгновенно.
| Метод | Путь | Право | Ответ |
|---|---|---|---|
| GET | /api/public-api/owner/payments/?status=&limit= | payments.read ⚠ | Журнал операций: сумма, комиссия Waitly, провайдер, статус. Токены карт не отдаются никогда |
| POST | /api/public-api/owner/orders/<id>/pay/ {"method":"CASH"} | orders.write + payments.write ⚠ | Приём наличных по поданному чеку (DELIVERED → PAID). Только CASH: без эквайринга, без фискального чека — фискализация остаётся на физической кассе |
Онлайн-оплаты, привязка карт и сплит-выплаты через паблик недоступны в принципе.