
REST API
Web API MiniShop3 (api.php) обслуживает витрину и headless-клиенты: корзина, checkout, ЛК покупателя, публичный каталог. Manager API (connector.php) — отдельно, под сессией MODX для Vue-админки.
Источник роутов витрины: core/components/minishop3/config/routes/web.php. Свои роуты: core/config/ms3_routes_web.custom.php, фрагменты аддонов: core/config/ms3.routes.d/web/*.php.
Точки входа
| Назначение | URL | Авторизация |
|---|---|---|
| Web API (витрина / headless) | /assets/components/minishop3/api.php | Токен: cookie ms3_token, Authorization: Bearer, legacy MS3TOKEN |
| Manager API (админка) | /assets/components/minishop3/connector.php | Сессия MODX |
Эта страница описывает Web API. Manager REST: Backend API, Маршрутизация.
Базовый URL
/assets/components/minishop3/api.php?route=/api/v1/{endpoint}Все запросы передают маршрут через параметр route. Для cookie-токена указывайте credentials: 'include'. CORS и rate limit настраиваются ключами ms3_cors_* и ms3_rate_limit_* (Системные настройки).
Карта эндпоинтов
Источник: config/routes/web.php. Группа /api/v1 всегда проходит CORS, rate limit и ServiceCheck.
| Метод | Путь | Токен |
|---|---|---|
POST | /cart/add | гостевой |
POST | /cart/remove | гостевой |
POST | /cart/change | гостевой |
POST | /cart/change-option | гостевой |
GET | /cart/get | гостевой |
POST | /cart/clean | гостевой |
GET | /order/get | гостевой |
POST | /order/add | гостевой |
POST | /order/set | гостевой |
POST | /order/remove | гостевой |
POST | /order/submit | гостевой |
POST | /order/clean | гостевой |
GET | /order/cost | гостевой |
GET | /order/cost/cart | гостевой |
GET | /order/cost/delivery | гостевой |
GET | /order/cost/payment | гостевой |
POST | /order/address/set | гостевой |
POST | /order/address/clean | гостевой |
GET | /order/delivery/validation-rules | гостевой |
GET | /order/delivery/required-fields | гостевой |
POST | /customer/login | нет |
POST | /customer/register | нет |
POST | /customer/logout | авторизованный |
POST | /customer/forgot-password | нет |
POST | /customer/reset-password | нет |
POST | /customer/add | авторизованный |
GET | /customer/token/get | нет |
GET | /customer/addresses | авторизованный |
GET | /customer/addresses/{id} | авторизованный |
POST | /customer/addresses | авторизованный |
PUT | /customer/addresses/{id} | авторизованный |
DELETE | /customer/addresses/{id} | авторизованный |
PUT | /customer/addresses/{id}/set-default | авторизованный |
PUT | /customer/profile | авторизованный |
POST | /customer/changeAddress | гостевой |
POST | /customer/email/resend-verification | авторизованный |
GET | /customer/email/verify | нет |
GET | /customer/orders | авторизованный |
GET | /customer/orders/{id} | авторизованный |
POST | /customer/orders/{id}/cancel | авторизованный |
GET | /product/get/{id} | нет |
GET | /product/list | нет |
GET | /health | нет |
«Гостевой» токен: GET /customer/token/get (корзина и черновик заказа). «Авторизованный»: после login / register.
Программное создание заказа без сессии (extras/cron) — не Web HTTP. См. ProgrammaticOrderService.
Авторизация
Получение токена
Перед работой с корзиной и заказами необходимо получить токен клиента:
GET /api/v1/customer/token/getОтвет:
{
"success": true,
"data": {
"token": "abc123def456..."
},
"message": ""
}Хранение токена (httpOnly cookie)
Начиная с версии 1.6, токен хранится в httpOnly cookie ms3_token. Сервер автоматически устанавливает cookie при получении/обновлении токена.
Безопасность
httpOnly cookie недоступна из JavaScript, что защищает токен от XSS-атак. Браузер автоматически передаёт cookie при каждом запросе.
Порядок разрешения токена на сервере (TokenMiddleware):
- Заголовок
Authorization: Bearer {token}(для мобильных приложений) - Заголовок
HTTP_MS3TOKEN(legacy) - httpOnly cookie
ms3_token(основной способ для веба)
Cookie настраивается с учётом параметров MODX-сессии: session_cookie_domain, session_cookie_path, session_cookie_secure, session_cookie_samesite.
Формат ответов
Все ответы имеют единый формат:
Успех:
{
"success": true,
"data": { ... },
"message": "Сообщение об успехе"
}Ошибка:
{
"success": false,
"message": "Описание ошибки",
"code": 400
}Корзина
Добавить товар
POST /api/v1/cart/addПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
id | int | Да | ID товара |
count | int | Нет | Количество (по умолчанию 1) |
options | object | Нет | Опции товара (цвет, размер и т.д.) |
render | array | Нет | Токены сниппетов для SSR |
Пример запроса:
fetch('/assets/components/minishop3/api.php?route=/api/v1/cart/add&ms3_token=' + token, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: 123,
count: 2,
options: {
color: 'Красный',
size: 'XL'
}
})
})Ответ:
{
"success": true,
"data": {
"last_key": "123_a1b2c3d4",
"cart": [
{
"key": "123_a1b2c3d4",
"id": 123,
"count": 2,
"price": 1500,
"cost": 3000,
"weight": 0.5,
"options": {"color": "Красный", "size": "XL"},
"name": "Товар",
"thumb": "/assets/images/product.jpg"
}
],
"status": {
"total_count": 2,
"total_cost": 3000,
"total_weight": 1.0,
"total_positions": 1
}
},
"message": "Товар добавлен в корзину"
}Изменить количество
POST /api/v1/cart/changeПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
product_key | string | Да | Уникальный ключ товара в корзине |
count | int | Да | Новое количество |
Пример:
{
"product_key": "123_a1b2c3d4",
"count": 5
}Удалить товар
POST /api/v1/cart/removeПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
product_key | string | Да | Уникальный ключ товара |
Изменить опции товара
POST /api/v1/cart/change-optionМеняет опции позиции в корзине (ключ позиции может измениться).
Параметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
product_key | string | Да | Ключ позиции в корзине |
options | object | Да | Новые опции (непустой объект) |
Пример:
{
"product_key": "123_a1b2c3d4",
"options": {
"color": "red",
"size": "L"
}
}Получить корзину
GET /api/v1/cart/getОтвет:
{
"success": true,
"data": {
"cart": [...],
"status": {
"total_count": 5,
"total_cost": 7500,
"total_weight": 2.5,
"total_positions": 3
}
}
}Очистить корзину
POST /api/v1/cart/cleanЗаказ
Получить черновик заказа
GET /api/v1/order/getОтвет:
{
"success": true,
"data": {
"order": {
"id": 0,
"delivery_id": 1,
"payment_id": 1,
"order_comment": "",
"cart_cost": 1500,
"delivery_cost": 300,
"cost": 1800,
"address_email": "user@example.com",
"address_phone": "+79991234567",
"address_first_name": "Иван",
"address_last_name": "Иванов",
"address_city": "Москва",
"address_street": "Ленина",
"address_comment": ""
}
}
}В data только объект order (поля msOrder + адрес с префиксом address_). Списки способов доставки и оплаты этот эндпоинт не отдаёт.
Добавить/обновить поле
POST /api/v1/order/addПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
key | string | Да | Имя поля |
value | mixed | Да | Значение |
Доступные поля:
| Поле | Описание |
|---|---|
email | Email (пишется в адрес) |
phone | Телефон (адрес) |
first_name | Имя (адрес) |
last_name | Фамилия (адрес) |
delivery_id | ID способа доставки |
payment_id | ID способа оплаты |
order_comment | Комментарий к заказу (msOrder) |
comment | Комментарий к адресу (msOrderAddress) |
city | Город |
street | Улица |
building | Дом |
room | Квартира/офис |
index | Индекс |
address_hash | Хеш сохранённого адреса |
В add / set ключи адреса — без префикса (city, first_name). В ответе order/get те же поля приходят как address_city, address_first_name.
Пример:
{
"key": "email",
"value": "user@example.com"
}Установить несколько полей
POST /api/v1/order/setПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
fields | object | Да | Объект с полями |
Пример:
{
"fields": {
"email": "user@example.com",
"phone": "+79991234567",
"first_name": "Иван",
"delivery_id": 1,
"payment_id": 2,
"order_comment": "Позвоните перед доставкой"
}
}При ошибке хотя бы одного поля ответ success: false, сообщение ms3_order_err_validation, в data:
{
"order": { },
"errors": {
"email": "…",
"delivery_id": "…"
}
}Каждое поле по-прежнему проходит через add() и события msOnBeforeAddToOrder / msOnAddToOrder. set() только агрегирует ошибки.
Удалить поле
POST /api/v1/order/removeПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
key | string | Да | Имя поля |
Оформить заказ
POST /api/v1/order/submitОтвет (успех):
{
"success": true,
"data": {
"order_id": 15,
"order_num": "24/12-15",
"redirect_url": "/thank-you?msorder=15"
},
"message": "Заказ успешно оформлен"
}Ответ (ошибка валидации):
{
"success": false,
"message": "Заполните обязательные поля",
"data": {
"errors": {
"email": "Укажите email",
"phone": "Укажите телефон"
}
},
"code": 400
}Очистить заказ
POST /api/v1/order/cleanСтоимость
Полная стоимость
GET /api/v1/order/costОтвет:
{
"success": true,
"data": {
"cart_cost": 5000,
"delivery_cost": 300,
"payment_cost": 0,
"total_cost": 5300,
"discount": 0
}
}Стоимость корзины
GET /api/v1/order/cost/cartСтоимость доставки
GET /api/v1/order/cost/deliveryКомиссия оплаты
GET /api/v1/order/cost/paymentАдреса доставки
Установить сохранённый адрес
POST /api/v1/order/address/setПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
address_hash | string | Да | MD5 хеш адреса |
Очистить адрес
POST /api/v1/order/address/cleanВалидация доставки
Правила валидации
GET /api/v1/order/delivery/validation-rulesОтвет:
{
"success": true,
"data": {
"city": {"required": true, "min": 2},
"street": {"required": true},
"building": {"required": true},
"phone": {"required": true, "pattern": "^\\+?[0-9]+$"}
}
}Обязательные поля
GET /api/v1/order/delivery/required-fieldsОтвет:
{
"success": true,
"data": ["city", "street", "building", "phone"]
}Клиент
Регистрация
POST /api/v1/customer/registerПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
email | string | Да | |
password | string | Да | Пароль |
first_name | string | Нет | Имя |
last_name | string | Нет | Фамилия |
phone | string | Нет | Телефон |
privacy_accepted | bool | Зависит от настроек | Согласие на обработку данных |
Контроллер передаёт в процессор только эти поля. password_confirm в Web API регистрации не используется (нужен для reset-password).
Ответ:
{
"success": true,
"object": {
"customer": {
"id": 5,
"email": "user@example.com",
"first_name": "Иван",
"last_name": "Петров",
"phone": "+79991234567",
"email_verified": false
},
"token": "abc123def456...",
"expires_at": "2026-03-16 12:34:56",
"email_verification_required": false,
"redirect_url": ""
},
"message": "Регистрация успешна"
}Breaking change (v1.6)
Формат ответа регистрации изменён:
- Было (v1.5):
token— объект{token: "...", expires_at: "..."} - Стало (v1.6):
token— строка,expires_atвынесен на верхний уровень
Кастомные темы, обращающиеся к result.object.token.token, нужно обновить на result.object.token.
Авторизация
POST /api/v1/customer/loginПараметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
email | string | Да | |
password | string | Да | Пароль |
Ответ:
{
"success": true,
"data": {
"customer_id": 5,
"token": "session_token_xyz789",
"expires_at": "2026-08-19 12:00:00",
"customer": {
"id": 5,
"email": "user@example.com",
"first_name": "Иван"
}
}
}Ротация токена
После login / register / успешного email/verify сервер всегда выдаёт новый API-токен (AuthManager::establishCustomerSession). Старый гостевой или предыдущий cookie ms3_token отзывается. Черновик корзины переносится на новый токен (transferDraftToToken / bindDraftToCustomer), затем session_regenerate_id(true).
Headless-клиент обязан сохранить новый token / expires_at из ответа. На витрине с httpOnly cookie браузер получает обновление cookie сам.
const res = await fetch('/api/v1/customer/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'ms3-token': guestToken, // текущий гостевой токен
},
body: JSON.stringify({ email, password }),
})
const json = await res.json()
if (!json.success) throw new Error(json.message)
// Обязательно подменить токен во всех следующих запросах
const { token, expires_at, customer_id } = json.data
localStorage.setItem('ms3_token', token)
localStorage.setItem('ms3_token_expires', expires_at)Выход
POST /api/v1/customer/logoutТребует авторизованного токена. Завершает сессию покупателя.
Восстановление пароля
POST /api/v1/customer/forgot-passwordТокен не нужен. Rate limit по email (1 запрос / 5 минут).
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
email | string | Да | Email покупателя |
Ответ всегда успешен с точки зрения UX (не раскрывает, есть ли аккаунт), письмо уходит только если клиент найден.
Сброс пароля
POST /api/v1/customer/reset-passwordТокен не нужен (токен сброса приходит в письме).
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
token | string | Да | Токен из письма |
password | string | Да | Новый пароль |
password_confirm | string | Да | Подтверждение пароля |
Обновить профиль
PUT /api/v1/customer/profileТребует авторизации (токен авторизованного клиента).
Параметры:
| Параметр | Тип | Описание |
|---|---|---|
first_name | string | Имя |
last_name | string | Фамилия |
phone | string | Телефон |
Быстрое обновление поля профиля
POST /api/v1/customer/addТребует авторизации. Обновляет одно поле msCustomer (key + value). Разрешены editable-ключи из xPDO-карты (с denylist служебных полей). Для email при смене сбрасывается email_verified_at.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
key | string | Да | Имя поля |
value | mixed | Да | Новое значение |
Выбрать сохранённый адрес в черновике
POST /api/v1/customer/changeAddressНужен гостевой токен. Аналог POST /order/address/set: в черновик подставляется адрес по address_hash (допускается legacy-поле value).
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
address_hash | string | Да | Hash адреса покупателя |
Верификация email
GET /api/v1/customer/email/verify?token=verification_tokenПовторная отправка верификации
POST /api/v1/customer/email/resend-verificationТребует авторизации.
Адреса клиента
Все endpoints требуют авторизации.
Список адресов
GET /api/v1/customer/addressesОтвет:
{
"success": true,
"data": [
{
"id": 1,
"hash": "abc123...",
"city": "Москва",
"street": "Ленина",
"building": "10",
"room": "5",
"is_default": true
}
]
}Получить адрес
GET /api/v1/customer/addresses/{id}Создать адрес
POST /api/v1/customer/addressesПараметры:
| Параметр | Тип | Описание |
|---|---|---|
city | string | Город |
street | string | Улица |
building | string | Дом |
room | string | Квартира/офис |
index | string | Индекс |
country | string | Страна |
region | string | Регион |
is_default | bool | Адрес по умолчанию |
Обновить адрес
PUT /api/v1/customer/addresses/{id}Удалить адрес
DELETE /api/v1/customer/addresses/{id}Установить адрес по умолчанию
PUT /api/v1/customer/addresses/{id}/set-defaultЗаказы клиента
Нужен токен авторизованного покупателя. Черновики в списке и карточке не отдаются.
Список заказов
GET /api/v1/customer/orders?limit=20&offset=0&status=2| Параметр | Тип | Описание |
|---|---|---|
limit | int | Размер страницы (по умолчанию 20, максимум 100) |
offset | int | Смещение |
status | int | Фильтр по status_id. Черновик и невалидные ID игнорируются |
Ответ data:
{
"orders": [
{
"id": 15,
"uuid": "...",
"num": "2603/1",
"cost": 3500,
"status_id": 2,
"status_name": "Новый",
"can_cancel": true
}
],
"total": 1,
"limit": 20,
"offset": 0
}Карточка заказа
GET /api/v1/customer/orders/{id}Возвращает заказ покупателя с позициями и связанными сущностями. 404, если заказ чужой, не найден или черновик.
Отмена заказа
POST /api/v1/customer/orders/{id}/cancelОтменяет заказ, если текущий статус входит в ms3_customer_cancel_allowed_statuses.
Ответ (успех):
{
"success": true,
"message": "Заказ отменён",
"data": {
"order_id": 15,
"status_id": 5
}
}Ошибки: 400 (статус не разрешён), 404 (не найден), 401 (нет авторизации).
Связанные настройки:
| Настройка | Описание |
|---|---|
ms3_customer_cancel_allowed_statuses | ID статусов, при которых разрешена отмена (по умолчанию 2,3) |
ms3_status_canceled | ID целевого статуса отмены |
Каталог товаров
Публичные эндпоинты. Токен не нужен. Отдаются только опубликованные, неудалённые товары без hidemenu в запрошенном (или текущем) контексте.
ProductCatalogService формирует ответ и обрезает его allowlist-ом полей ресурса и msProductData. Плагин на msOnGetProductFields может менять значения существующих ключей, но не добавить произвольные поля в JSON каталога. Для headless-витрины без msProducts см. также Каталог.
Один товар
GET /api/v1/product/get/{id}| Параметр пути | Описание |
|---|---|
id | ID ресурса товара |
400 без id, 404 если товар не найден или не публичный.
Список товаров
GET /api/v1/product/list?parent=5&limit=20&page=1&sort=price&dir=ASC| Параметр | Тип | Описание |
|---|---|---|
parent / category | int | ID родительской категории (основной parent ресурса) |
limit | int | По умолчанию 20, максимум 100 |
offset | int | Смещение. Альтернатива: page (с 1) |
page | int | Номер страницы, если offset не задан |
sort | string | id, pagetitle, menuindex, createdon, publishedon, price, article |
dir / sortdir | string | ASC или DESC |
query | string | Поиск по pagetitle / article |
context | string | Ключ контекста MODX |
include_options | 0 / 1 | Включить опции (по умолчанию 0) |
include_content | 0 / 1 | Включить content (по умолчанию 0) |
Ответ data:
{
"items": [ { "id": 10, "pagetitle": "Товар", "price": 1500 } ],
"total": 42,
"limit": 20,
"offset": 0
}Health Check
GET /api/v1/healthОтвет:
{
"success": true,
"data": {
"status": "ok",
"version": "1.0.0",
"timestamp": 1703952000,
"api": "web"
}
}Middleware
CORS
Настраивается через системную настройку ms3_cors_allowed_origins:
*— разрешить все доменыhttps://example.com,https://shop.example.com— список доменов
Rate Limiting
Защита от злоупотреблений через системные настройки:
ms3_rate_limit_max_attempts— максимум запросов (по умолчанию 60)ms3_rate_limit_decay_seconds— период в секундах (по умолчанию 60)ms3_rate_limit_store— хранилище счётчиков:file,redis,memcached(по умолчаниюfile)ms3_rate_limit_storage_path— каталог дляfile(пусто = системный temp)ms3_rate_limit_redis_dsn— DSN Redis (если задан, перекрывает host/port)ms3_rate_limit_redis_host/ms3_rate_limit_redis_port/ms3_rate_limit_redis_password/ms3_rate_limit_redis_databasems3_rate_limit_memcached_servers— серверы Memcached (по умолчанию127.0.0.1:11211)
При превышении лимита возвращается:
{
"success": false,
"message": "Too many requests",
"code": 429
}SSR (Server-Side Rendering)
API поддерживает серверный рендеринг HTML для обновления частей страницы.
Использование
Передайте массив токенов сниппетов в параметре render:
fetch('/api/v1/cart/add?ms3_token=' + token, {
method: 'POST',
body: JSON.stringify({
id: 123,
render: ['ms3_abc123...', 'ms3_def456...']
})
})Ответ включает HTML:
{
"success": true,
"data": {
"cart": [...],
"status": {...},
"render": {
"ms3_abc123...": "<div class=\"cart\">...</div>",
"ms3_def456...": "<span class=\"count\">5</span>"
}
}
}Регистрация сниппетов
Токены генерируются автоматически при вызове сниппетов с параметром selector:
{'msCart' | snippet : [
'tpl' => 'tpl.msCart',
'selector' => '#cart-container'
]}Кастомные роуты
Для добавления собственных endpoints создайте файл:
core/config/ms3_routes_web.custom.phpПример:
<?php
use MiniShop3\Router\Response;
$router->group('/api/v1', function($router) use ($modx) {
$router->get('/custom/endpoint', function($params) use ($modx) {
return Response::success(['custom' => 'data']);
});
});Кастомные роуты загружаются после системных и могут их переопределять.
JavaScript клиент
MiniShop3 предоставляет JavaScript библиотеку для работы с API:
// Добавить в корзину
await ms3.cartAPI.add(123, 2, { color: 'red' })
// Оформить заказ
const result = await ms3.orderAPI.submit()
// Хуки
ms3Hooks.addHook('afterAddCart', async ({ response }) => {
console.log('Товар добавлен', response.data)
})Подробнее в разделе Frontend JavaScript.
Коды ошибок
| Код | Описание |
|---|---|
| 400 | Неверный запрос (отсутствуют параметры, ошибка валидации) |
| 401 | Требуется токен авторизации |
| 403 | Доступ запрещён |
| 404 | Ресурс не найден |
| 429 | Превышен лимит запросов |
| 500 | Внутренняя ошибка сервера |
Отладка
Включите режим отладки через настройку ms3_api_debug:
{
"success": false,
"message": "Internal server error",
"code": 500,
"debug": {
"exception": "Exception",
"message": "Detailed error message",
"file": "/path/to/file.php",
"line": 123
}
}Безопасность
Не включайте режим отладки на продакшене — он раскрывает внутреннюю структуру приложения.
