Обзор
Все методы живут под /api/v1 и отвечают JSON. Организация определяется
по токену — передавать её идентификатор не нужно и нельзя.
Данные разделены на двух уровнях. Организация берётся из токена:
увидеть чужие данные нельзя даже при подстановке идентификаторов. Филиал
для большинства методов задаётся заголовком X-Branch-Id — токен может иметь
доступ к нескольким филиалам, и система должна понимать, о каком идёт речь.
Метод приёма заказов работает без X-Branch-Id:
филиал приходит в теле запроса. Так витрина или агрегатор может слать заказы
во все точки организации одним токеном.
Аутентификация
Bearer-токены Sanctum. Токен передаётся заголовком Authorization: Bearer <token>
в каждом запросе.
Вход по логину и паролю. Без авторизации.
{
"login": "manager@example.com",
"password": "secret",
"device_name": "Integration server"
}
{
"message": "Успешная авторизация.",
"data": {
"user": { "id": 12, "name": "Иван Петров", "…": "…" },
"token": "17|xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"expires_at": "2026-09-06T10:00:00+05:00"
}
}
{ "message": "Ошибка авторизации.", "errors": { "login": ["…"] } }
Вход по PIN-коду — для кассовых терминалов, где не набирают пароль.
Текущий пользователь, его организация, филиалы и права.
Отзывает текущий токен. /auth/logout-all отзывает все токены пользователя.
Токены для интеграций
Для сервер-серверных интеграций логин с паролем не нужен и вреден: пароль сотрудника меняется, токен умирает. Вместо этого выпускается служебный токен с ограниченными правами — командой на стороне ресторана:
php artisan integration:create-token
Такой токен получает права integration:read и integration:write.
Метод приёма заказов требует именно integration:write — обычный
пользовательский токен его не откроет.
Заголовки
| Заголовок | Когда нужен |
|---|---|
| Authorization | Bearer <token> — везде, кроме входа и регистрации. |
| Accept | application/json — иначе на ошибках валидации вернётся HTML вместо JSON. |
| X-Branch-Id |
Филиал для методов меню, заказов, склада, кассы, доставки. Альтернатива —
поле branch_id в теле запроса или параметре маршрута.
|
| Idempotency-Key |
Обязателен на записи через /integrations/*. Формат — UUID.
Тот же ключ с тем же телом вернёт исходный результат, с другим — конфликт.
|
Ответы и ошибки
Успешный ответ содержит data, ошибка — message и подробности.
Списки приходят с пагинацией: параметры page и per_page, максимум 100 на страницу.
{ "success": true, "data": { "…": "…" }, "message": "Order accepted" }
{
"success": false,
"error": {
"code": "UNKNOWN_PRODUCTS",
"message": "Товары не найдены: 412, 980.",
"details": { "product_ids": [412, 980] }
}
}
{ "message": "…", "errors": { "items": ["Заказ не может быть пустым."] } }
| Код | Значение |
|---|---|
| 200 | успешно; для повторного заказа — уже принят ранее |
| 201 | создано |
| 401 | токен не передан, истёк или отозван |
| 403 | у токена нет нужного права или доступа к филиалу |
| 404 | объект не найден или принадлежит другой организации |
| 409 | конфликт идемпотентности |
| 422 | ошибка валидации или бизнес-правила |
| 429 | превышен лимит частоты |
Лимиты
| Группа | Лимит | Считается по |
|---|---|---|
| Вход и регистрация | 5 / мин | IP-адресу |
| Остальные методы | 60 / мин | пользователю, иначе IP |
При превышении приходит 429 с заголовком Retry-After.
Если интеграции штатно не хватает 60 запросов в минуту — это повод обсудить лимит,
а не обходить его несколькими токенами.
Приём заказов
Главный метод для внешних витрин и агрегаторов. Заказ приходит без стола, кассира и открытой смены — у него отдельная точка входа, а не «подогнанный» обычный метод.
Требует право integration:write и заголовок Idempotency-Key в формате UUID.
{
"external_id": "SHOP-2026-000123",
"source": "lolotea",
"type": "delivery",
"branch_id": 2,
"customer": {
"name": "Иван Петров",
"phone": "+998901234567",
"email": "ivan@example.com"
},
"items": [
{
"product_id": 434,
"quantity": 2,
"unit_price": 22000,
"notes": "без сахара",
"modifiers": [
{ "modifier_id": 89, "quantity": 1, "price_delta": 3000 }
]
}
],
"totals": {
"subtotal": 44000,
"discount": 0,
"delivery_fee": 10000,
"total": 54000,
"currency": "UZS"
},
"payment": {
"method": "click",
"status": "paid",
"paid_amount": 54000,
"external_transaction_id": "clk_88213"
},
"delivery": {
"address": "Ташкент, ул. Амира Темура, 5",
"lat": 41.311081,
"lng": 69.240562,
"scheduled_at": "2026-08-07T18:30:00+05:00"
},
"notes": "Позвонить за 10 минут"
}
{
"success": true,
"data": {
"order": {
"id": "973053a3-008f-47a7-ba3d-9598cc95dd4f",
"order_number": "20260807-0002",
"external_id": "SHOP-2026-000123",
"status": "new",
"type": "delivery"
}
},
"message": "Order accepted"
}
Обязательные поля
| Поле | Правило |
|---|---|
| external_id | строка до 64 символов — ваш номер заказа, по нему считается уникальность |
| type | delivery или pickup |
| branch_id | идентификатор филиала в RestoPOS |
| items | минимум одна позиция |
| items[].product_id | идентификатор товара; неизвестный отклоняет заказ целиком |
| items[].quantity | целое, минимум 1 |
| items[].unit_price | цена за единицу без модификаторов |
| totals.subtotal | сумма позиций |
| totals.total | итог к оплате |
| payment.method | cash, click или payme |
| payment.status | paid или unpaid |
Повторная отправка
Сеть рвётся, ответ теряется — повторяйте запрос с тем же Idempotency-Key.
Тот же ключ с тем же телом вернёт исходный заказ и код 200 вместо 201,
второй заказ на кухню не уйдёт. Тот же ключ с другим телом — это ошибка на вашей
стороне, и она честно возвращается конфликтом.
| Код ошибки | HTTP | Когда |
|---|---|---|
| UNKNOWN_BRANCH | 422 | филиала нет в вашей организации |
| UNKNOWN_PRODUCTS | 422 | товары не найдены; список — в details.product_ids |
| UNKNOWN_MODIFIERS | 422 | модификаторы не найдены |
| IDEMPOTENCY_CONFLICT | 409 | ключ уже использован с другим телом запроса |
Частичный приём невозможен: половина корзины на кухне хуже честного отказа. Практически это означает, что ваш каталог разошёлся с нашим — синхронизируйте товары и повторите отправку.
Заказы
Работа с заказами в зале. Для приёма заказов извне используйте отдельный метод — этот рассчитан на кассира и открытую смену.
| Метод | Назначение |
|---|---|
| GET /orders | список с фильтрами |
| POST /orders | создать заказ |
| GET /orders/{id} | карточка заказа |
| POST /orders/{id}/items | добавить позицию |
| PATCH /orders/{id}/items/{item} | изменить позицию |
| DELETE /orders/{id}/items/{item} | удалить позицию |
| POST /orders/{id}/send-to-kitchen | отправить на кухню |
| POST /orders/{id}/discount | применить скидку |
| POST /orders/{id}/transfer | перенести на другой стол |
| POST /orders/{id}/close | закрыть заказ |
| POST /orders/{id}/cancel | отменить заказ |
Изменение состава идёт через методы позиций, а смена состояния — через действия
(close, cancel). Так каждое изменение проходит бизнес-правила:
списание со склада, пересчёт сумм, проверку смены.
Клиенты
| Метод | Назначение |
|---|---|
| GET /customers | список клиентов |
| GET /customers/search | поиск по имени или телефону |
| POST /customers | создать клиента |
| GET /customers/{id}/history | история заказов |
| POST /customers/{id}/bonus | начислить или списать бонусы |
Склад
Учёт ведётся по ингредиентам, а не по готовым блюдам — остаток товара выводится из техкарты.
| Метод | Назначение |
|---|---|
| GET /warehouse/stock | текущие остатки |
| GET /warehouse/stock/low | позиции ниже минимального остатка |
| POST /warehouse/stock/adjust | корректировка остатка |
| GET /warehouse/supplies | поставки |
| POST /warehouse/supplies | создать поставку |
| POST /warehouse/supplies/{id}/receive | оприходовать поставку |
Доставка
| Метод | Назначение |
|---|---|
| POST /delivery/zones/check-point | попадает ли точка в зону доставки |
| GET /delivery/zones | зоны доставки |
| GET /delivery/couriers | курьеры |
| PATCH /delivery/couriers/{id}/location | обновить координаты курьера |
| PATCH /delivery/couriers/{id}/status | сменить статус курьера |
| GET /delivery/couriers/{id}/deliveries | активные доставки курьера |
| GET /delivery/orders | заказы на доставку |
| POST /delivery/orders/{id}/assign | назначить курьера |
| POST /delivery/orders/{id}/pickup | курьер забрал заказ |
| POST /delivery/orders/{id}/deliver | заказ доставлен |
| POST /delivery/orders/{id}/rate | оценка доставки |
Проверка зоны полезна до создания заказа: она отвечает, обслуживается ли адрес, и позволяет не принимать заказ, который потом придётся отменять.
Отчёты и финансы
| Метод | Назначение |
|---|---|
| GET /reports/dashboard | сводка за период |
| GET /reports/sales | продажи |
| GET /reports/products | продажи по товарам |
| GET /reports/employees | показатели сотрудников |
| GET /reports/export/{type} | выгрузка отчёта файлом |
| GET /finance/pnl | отчёт о прибылях и убытках |
| GET /finance/cash-flow | движение денежных средств |
| GET /finance/analytics | финансовая аналитика |
| GET /cash-shifts/current | текущая кассовая смена |
| GET /cash-shifts/{id}/report | отчёт по смене |
Все методы ядра
Кроме перечисленного, в API есть разделы залов и столов, бронирований, KDS, сотрудников, принтеров и уведомлений — они рассчитаны на внутренние приложения и в этот справочник не вынесены.
Полная машинописная спецификация лежит в репозитории: docs/api_specification.yaml,
формат OpenAPI 3.0.3 — её можно открыть в любом редакторе Swagger и сгенерировать клиент.
Напишите нам, что вы пытаетесь построить. Часто задача решается уже существующим методом, а если нет — это повод добавить его в публичную часть API, а не выдавать доступ к внутреннему.