Бесплатно
Загрузите дополнение из админки вашего сайта.
Как загрузить?
Как загрузить?
Зачем
Публичный API у сайта обычно вырастает из одного сниппета «отдать заказы партнёру», а дальше обрастает вторым, третьим и собственной проверкой токена в каждом. mxApi забирает на себя всё, что в этих задачах одинаково — маршрутизацию, аутентификацию, права, лимиты, журнал, — и оставляет разработчику только сам эндпоинт.
Что делает
Сами эндпоинты mxApi не поставляет — их приносят провайдеры: другое дополнение или код конкретного сайта. Эндпоинт поверх готового процессора MODX описывается одним классом-паспортом: маршрут, метод, scope, право, параметры и примеры. Всё остальное — разбор запроса, приведение типов, пагинация, ошибки, документация в каталоге — делает пакет.
Две линии
Публичный API у сайта обычно вырастает из одного сниппета «отдать заказы партнёру», а дальше обрастает вторым, третьим и собственной проверкой токена в каждом. mxApi забирает на себя всё, что в этих задачах одинаково — маршрутизацию, аутентификацию, права, лимиты, журнал, — и оставляет разработчику только сам эндпоинт.
Что делает
- Маршруты под своим префиксом (по умолчанию
), не пересекающиеся с маршрутами сайта и других дополнений./mxapi/v1 - Два способа получить токен: по логину и паролю пользователя MODX и по паре client_id / client_secret машинного клиента. Клиенты интеграций заводятся прямо в админке — на вкладке «mxApi» страницы пользователя.
- Права не изобретаются заново. Запрос выполняется от имени реального пользователя MODX, а право эндпоинта проверяется штатным механизмом политик. Дать интеграции больше, чем есть у её пользователя, нельзя.
- Токен непрозрачный и живёт в базе — отзывается мгновенно. В таблице хранится только sha256-хэш: утечка базы не даёт доступа.
- Каталог эндпоинтов в админке и OpenAPI 3.0 собираются из живого реестра. Отдельного YAML-файла, который расходится с кодом, не существует.
- Лимит частоты запросов, идемпотентность изменяющих вызовов по заголовку Idempotency-Key и журнал обращений: кто, когда, каким токеном, в каком контексте MODX и с каким результатом.
Сами эндпоинты mxApi не поставляет — их приносят провайдеры: другое дополнение или код конкретного сайта. Эндпоинт поверх готового процессора MODX описывается одним классом-паспортом: маршрут, метод, scope, право, параметры и примеры. Всё остальное — разбор запроса, приведение типов, пагинация, ошибки, документация в каталоге — делает пакет.
Две линии
- версии 1.x — для MODX Revolution 2.6+, PHP 7.4+;
- версии 2.x — для MODX Revolution 3, PHP 8.1+; интерфейсу в админке нужен бесплатный пакет VueTools.
2.1.0
- FEATURE: промежуточные обработчики (middleware) регистрируются пакетом через событие mxApiOnRegisterMiddleware. Прежде их можно было подключить только файлом core/config/mxapi.php, то есть кодом конкретного сайта: пакет-провайдер, которому нужна своя проверка на каждом запросе, вынужден был просить администратора править конфиг руками. Обработчики пакетов встают в цепочку перед обработчиками сайта — сайт остаётся последним словом; класс, не реализующий MiddlewareInterface, отклоняется с записью в лог, а сломавшийся при создании — не рушит API и не выкидывает из цепочки соседей.
- FEATURE: HTTP-валидация кэша по ETag / If-None-Match. Эндпоинт объявляет в паспорте 'cache' => EndpointMetadata::CACHE_ETAG, и ядро само считает метку от тела ответа, отдаёт её заголовком ETag и отвечает 304 без тела, когда клиент прислал ту же метку в If-None-Match. Для больших инкрементальных выгрузок это разница между «перекачать всё» и «узнать, что ничего не поменялось». По умолчанию — CACHE_NO_STORE, поведение существующих эндпоинтов не меняется.
- FEATURE: MxApi\Core\Endpoint\EtagAwareInterface — эндпоинт может объявить версию ответа ДО его сборки (computeEtag), и тогда при совпадении обработчик вообще не выполняется: выборка не делается, процессор не зовётся. Отдельный интерфейс, а не новый метод EndpointInterface: добавление метода в интерфейс сломало бы все установленные провайдеры молча.
- CHANGE: метка версии считается ВНУТРИ цепочки обработчиков, а не до неё. Иначе повторный запрос с If-None-Match проходил бы мимо счётчика лимита частоты, и заголовком можно было бы дёшево обойти ограничение. Поведение закреплено тестом.
- FEATURE: курсорная (keyset) пагинация для инкрементальных выгрузок. AbstractEndpoint::readCursor() / nextCursor() дают эндпоинту непрозрачный курсор, а клиент получает его в meta.next_cursor вместе с meta.has_more и передаёт как есть в параметре cursor. В отличие от offset, курсор не разъезжается, когда данные меняются между страницами, и не заставляет базу пролистывать пропущенные строки. meta.total в курсорном режиме не отдаётся намеренно: второй полный проход по выборке обесценивает саму идею — вместо него читается на одну запись больше запрошенного и по ней выставляется has_more.
- FEATURE: курсор подписывается HMAC-SHA256 и привязан к отпечатку выборки. Внутри курсора — позиция обхода, то есть параметры запроса к базе; без подписи это был бы произвольный ввод. Подделанный курсор, курсор с другого сайта и курсор, полученный для другого набора фильтров, отбрасываются с ошибкой invalid_parameter, а состав ключей позиции остаётся деталью реализации эндпоинта.
- FEATURE: системная настройка mxapi.cursor_secret — ключ подписи курсоров. Пакет привозит её пустой, значение генерируется резолвером при установке и на каждом сайте своё; заполняется только пустая настройка, чтобы обновление пакета не обесценивало курсоры работающих интеграций. Пустое значение курсорную пагинацию выключает (ошибка internal_error), а не разрешает неподписанные курсоры.
- CHANGE: OpenAPI описывает новое честно: у эндпоинтов с ETag появились заголовок ETag в ответе 200 и ответ 304 без тела, а в общей схеме успешного ответа — поля meta.has_more и meta.next_cursor. Недокументированный 304 клиент разберёт как отказ — пустое тело там, где обещан конверт success/data.
- CHANGE: граница публичного контракта проведена явно. Каждый класс ядра объявляет в docblock, что он такое: @api — обещание стороннему коду (AbstractEndpoint, EndpointInterface, EndpointMetadata, ParameterMetadata, ProcessorEndpoint, Request, Response, Config, ApiException, ProviderInterface, MiddlewareInterface, EtagAwareInterface, ...), @internal — внутренняя механика, которая переписывается свободно (Kernel, Router, реестр, репозитории, ETag, Cursor). До сих пор граница жила в головах, и интегратор не мог отличить обещание от детали реализации.
- CHANGE: контракт закреплён тестом (tests/PublicContractTest.php): сигнатуры и константы @api-классов сверяются со слепком в tests/fixtures/public-contract.txt, а класс ядра без тега @api/@internal роняет тест. Правка публичной сигнатуры теперь обязана быть осознанной — со сменой версии и записью здесь; diff слепка и есть список того, что меняется у интеграторов.
- CHANGE: паритет ядра между линиями MODX 2 и MODX 3 проверяется тестом (tests/CoreParityTest.php), а не внимательностью. Каталог src/Core в обеих линиях побайтово одинаков; общий манифест хэшей лежит в обеих, поэтому забытый перенос правки роняет тесты той линии, куда её не донесли. Раньше расхождение обнаружилось бы у интегратора, который пишет провайдера один раз на обе версии MODX.
- CHANGE: снята пометка beta. Публичный контракт зафиксирован и проверяется тестом, поэтому дальше линия развивается по semver: ломающая правка @api-класса означает смену старшей цифры.
2.0.2-beta
- FIX: любой запрос к API отдавал 500 там, где установлен mxLogger. Платформа звала логгер с перепутанным порядком аргументов — log($message, $context, $level, 'mxapi') вместо его настоящей сигнатуры log($tags, $level, $message, array $context): четвёртым параметром уходила строка вместо массива, и mxLogger падал с TypeError. Ошибка вылезала на пути каждого запроса, потому что первым в лог пишет плановая уборка, а она выполняется по ходу обычного ответа.
- FIX: TypeError и прочие \Error проходили мимо всех защитных catch — они ловили только \Exception, а ошибки типов в PHP 7+ наследуются от \Error и \Exception не являются. Из-за этого сбой одного чужого куска кода — логгера, стороннего провайдера, эндпоинта, записи в журнал — ронял ответ целиком вместо аккуратной ошибки в JSON. Теперь ловится \Throwable в пяти местах: глобальный обработчик запроса, регистрация провайдера, плановая уборка, запись журнала и вызов mxLogger.
- CHANGE: в mxLogger уходит чистое сообщение, без префикса "[mxapi]" и без дампа контекста в тексте. Тэг и контекст у логгера — отдельные поля грида, дублировать их в сообщении значит ломать фильтрацию, ради которой логгер и подключают. Префикс и контекст строкой остались только в запасном пути — журнале MODX, где отдельных полей под них нет.
- CHANGE: логгер ищется двумя способами в обеих линиях — фасад $modx->mxl (mxLogger 1.2+) и сервис. Прежде двойка проверяла только $modx->mxlogger, а тройка — только контейнер; при выключенном плагине фасада или при незваном getService() логи молча уходили мимо.
- CHANGE: логирование платформы покрыто тестами (tests/PlatformLoggingTest.php). Заглушка mxLogger объявляет его настоящую сигнатуру с типизированным array $context, поэтому неверный порядок аргументов ловится тестом, а не глазами. Отдельно проверяется, что сломанный логгер, сломанный провайдер и сломанный эндпоинт не рушат ответ.
2.0.1-beta
- FIX: уборка просроченных токенов и старых записей журнала роняла ЛЮБОЙ запрос к API. Условия удаления передавались объектом запроса, тогда как xPDO::removeCollection() строит запрос сам и кладёт второй аргумент внутрь условий как значение: при сборке SQL объект приводился к строке, и PHP падал с fatal (Object of class xPDOQuery_mysql could not be converted to string). Ловушка была в том, что getCount() объект принимает — подсчёт работал, а удаление тем же аргументом падало. Уборка выполняется по ходу обычных запросов, поэтому 500 отдавал любой эндпоинт, а не только вызванный; пока просроченных записей не появилось, пакет выглядел рабочим.
- CHANGE: уборка токенов и журнала покрыта тестами — прежде этих методов в тестах не было вовсе. Проверяется, что условия уходят в удаление массивом, что бессрочный токен клиента (expireson = 0) уборка не трогает и что при отсутствии просроченных записей удаление не вызывается.
2.0.0-beta
- CHANGE: линия MODX 3. Порт версии 1.0.0-beta с MODX Revolution 2 на 3; публичный контракт (маршруты, токены, scope, каталог, OpenAPI, имена таблиц) не изменился, переписан слой платформы, загрузки и админки. Требования: MODX 3.0+, PHP 8.1+.
- CHANGE: платформенный адаптер Modx3Platform и репозитории на xPDO 3. Ядро (MxApi\Core*) не менялось вовсе — оно не знает про modX, и это условие проверяется тестом ядра, общим для обеих линий.
- CHANGE: модели переехали в PSR-4 (MxApi\Model\MxApiClient, ...MxApiToken, ...MxApiLog). Имена таблиц прежние — mxapi_client, mxapi_token, mxapi_log, — поэтому данные интеграций и аудит переносятся с сайта на MODX 2 как есть.
- CHANGE: сервис mxApi регистрируется в DI-контейнере MODX 3 (bootstrap.php, $modx->services->get('mxapi')); getService() из двойки в тройке мёртв.
- CHANGE: процессоры админки — namespaced (MxApi\Processors\Mgr...), коннектор принимает FQCN в action: короткое имя процессора MODX 3 не находит.
- CHANGE: интерфейс админки переписан на Vue 3 поверх пакета VueTools вместо ExtJS-виджетов двойки. Каталог эндпоинтов, вкладка «mxApi» на странице правки пользователя, окно клиента с выбором scope по источникам и разовый показ секрета — поведение прежнее. Строки интерфейса живут в лексиконе пакета.
- CHANGE: тёмный режим PrimeVue отключён намеренно — менеджер MODX 3.2 своего тёмного режима не имеет, и виджет чернел бы внутри светлой админки на машине с тёмной системной темой.
- CHANGE: плагин mxApiUserClients поставляется файлом в пакете (static element). В линии MODX 2 сборщик брал его из базы стенда, и он мог молча не попасть в transport.
- CHANGE: сборка — shevartv/modx-builder (modxapp), локально, без стенда.
1.0.0-beta
- DOCS: docs/readme.txt переписан под порядок работы с пакетом: установка, правила веб-сервера для nginx И для Apache (в том числе передача заголовка Authorization, без которой bearer-токен теряется), выдача прав в MODX, получение токена ОБОИМИ способами — по логину/паролю и по client_id/client_secret с заведением клиента в админке, — вызов эндпоинтов, отзыв токена, каталог, коды ошибок и настройки. Прежний readme описывал client_credentials одной строкой в списке фич и не показывал, где берутся client_id и секрет.
- DOCS: docs/providers.md убран из пакета, содержимое переехало в публичную доку (docs.modx.pro/components/mxapi/providers). Markdown в transport никто не читает, а две копии одного текста расходятся молча.
- FEATURE: вкладка "mxApi" на странице правки пользователя — клиенты интеграции заводятся в админке, а не SQL-скриптом. Кнопка "Добавить", в окне название и выбор scope деревом по источникам (всё / весь источник / поштучно); ветки с уже выданными scope раскрыты. Предлагаются только те scope, на которые у пользователя есть права MODX: выдать больше его прав всё равно нельзя, а обещать это в форме — врать. Плагин mxApiUserClients на OnManagerPageBeforeRender, право save_user (кто может сменить пользователю пароль, тот и так выпустит токен от его имени).
- FEATURE: у клиента появилось собственное время жизни токена (поле token_ttl): 0 — общее значение сайта, -1 — бессрочно, больше нуля — своё. Раньше TTL был один на весь сайт, и укоротить токен одной интеграции можно было только всем сразу; свой rate_limit у клиента при этом уже был. Бессрочный токен пишется нулём в expireson — значение, которое проверка и уборка уже понимали.
- FEATURE: действия над клиентом продублированы кнопками-иконками в строке грида (изменить, перевыпустить, включить/отключить, удалить; штатный FontAwesome менеджера, названия в подсказке) — контекстное меню остаётся, но о нём надо догадаться. Список scope источника раскрывается кликом по названию источника, а не только по стрелке в несколько пикселей.
- FEATURE: перевыпуск секрета клиента с необязательным отзывом выданных токенов. Перевыпуск сам по себе их не гасит: плановая ротация не должна ронять работающую интеграцию, а компрометация обязана.
- FEATURE: MxApi\Core\Auth\ClientSecret — генерация ключа, секрета и его проверка в одном классе. Проверка была приватным методом TokenService, генерации не существовало вовсе; с выпуском секретов из админки формат хэша описывался бы в двух местах и разъехался бы молча.
- FEATURE: контекст MODX стал частью паспорта эндпоинта (modx_context): конкретный ключ, "request" (из заголовка X-MxApi-Context) или пусто. Ядро переключает контекст ДО проверки права эндпоинта — права процессоров принадлежат политике контекста, и проверка в одном контексте с исполнением в другом расходится.
- FEATURE: allow-list контекстов у клиента интеграции (поле contexts у mxApiClient). Пусто — только контекст по умолчанию, "*" — любой: иначе на мультисайте токен одного сайта работал бы на всех.
- FEATURE: настройки mxapi.context (контекст по умолчанию, mgr) и mxapi.allow_request_context (разрешить выбор контекста запросом, по умолчанию выключено).
- FEATURE: коды ошибок unknown_context (400) и context_not_allowed (403); контекст вызова пишется в журнал (mxApiLog.context).
- FIX: проверка прав fail-closed при непроверяемой сессии. modAccessibleObject::checkPolicy() выполняет проверку только при SESSION_STATE_INITIALIZED, иначе возвращает true — то есть без сессии права молча не проверялись вовсе. Адаптер поднимает сессию сам и отказывает, если это невозможно.
- FIX: смена контекста больше не теряет пользователя запроса. modX::switchContext() сбрасывает $modx->user и берёт его из сессии, поэтому после переключения пользователь привязывается заново, иначе процессор выполнялся бы от анонима.
- FEATURE: хук ProcessorEndpoint::extraMeta() — списочный процессор может отдать агрегаты по всей выборке (у miniShop2 это суммы и количество заказов за период) в meta, не смешивая их с data.
- FEATURE: хуки ProcessorEndpoint::beforeRun() и transformPayload() — провайдер готовит окружение процессора и нормализует ответ, не копируя handle().
- FIX: в журнал пишется контекст ЗАПУСКА эндпоинта, а не состояние платформы на момент записи. Процессоры miniShop2 сами уходят в контекст заказа (msOrder.context), и запись update заказа помечалась ctx=web, хотя эндпоинт объявлен и проверен в mgr.
- CHANGE: маршрут в публичном каталоге и OpenAPI отдаётся без шаблонов FastRoute (/ms2/orders/{id} вместо /ms2/orders/{id:\d+}) — приведение переехало в EndpointMetadata::getPublicPath().
- FIX: CMP не открывался вовсе — контроллер присваивал MxApi = {config: ...} после загрузки mxapi.js и затирал объект с методами (страница падала на «MxApi.init is not a function»). Теперь config дописывается в существующий объект.
- FIX: каталог отвечал «Доступ запрещён» — ручной XHR не передавал HTTP_MODAUTH, без которого коннектор MODX отвечает 401. Выгрузка OpenAPI уходит POST-формой, чтобы токен сессии не попадал в адресную строку.
- FIX: страница каталога не прокручивалась (менеджер держит body в overflow: hidden) — высота берётся от вьюпорта, прокручивается список, поиск и кнопки остаются на месте.
- FIX: раскрытие описания эндпоинта отбрасывало список в начало: перерисовка сбрасывала scrollTop, теперь позиция сохраняется, а раскрытая карточка при необходимости подтягивается минимальной прокруткой.
- CHANGE: в списке каталога маршрут показывается без шаблонов роутера, полный шаблон — в деталях эндпоинта.
- FEATURE: подписи и описания всех системных настроек (ru/en) и раскладка по областям: доступ и контексты, лимиты и пагинация, журнал и отладка. Без лексикона грид настроек показывал сырые ключи.
- FIX: резолвер приводит area существующих настроек к текущей раскладке — установка пакета их не обновляет (UPDATE_OBJECT = false, иначе затирались бы значения администратора) — и удаляет строки без ключа.
- FEATURE: настройка mxapi.catalog_filter — что показывать в /meta/endpoints и OpenAPI: all (весь публичный контракт, по умолчанию), scope (только вызываемое предъявленным токеном) или permission (только то, на что есть право MODX). Раньше каталог всегда отдавал весь контракт, и на сайте с несколькими интеграциями клиент одной видел эндпоинты другой вместе с именами прав. Активный режим возвращается в meta.filter.
- CHANGE: настройки mxapi.providers и mxapi.middleware удалены (и вычищаются из базы резолвером). Имя класса в системной настройке означало, что состав API живёт в базе и разъезжается с кодом при переносе дампа, а правка настроек в админке влияла на то, какие классы инстанцирует ядро. Остались два пути: событие mxApiOnRegisterEndpoints для пакетов и ключи providers/middleware в core/config/mxapi.php для кода сайта.
- BUILD: каркас пакета: билдер modxbuilder/mxapi, composer-автозагрузка PSR-4 (MxApi\ → src/), зависимость nikic/fast-route.
- FEATURE: модель данных — mxApiClient (клиенты интеграций, секрет только хэшем), mxApiToken (bearer-токены, в БД только sha256-хэш), mxApiLog (журнал вызовов и аудит).
- FEATURE: политика доступа mxapiTemplate/mxapiDefault с обязательным правом load — без него non-sudo пользователь получал отказ ещё до проверки прав эндпоинта.
- FEATURE: системные события mxApiOnRegisterEndpoints, mxApiOnBeforeRequest, mxApiOnBeforeEndpointRun, mxApiOnAfterEndpointRun, mxApiOnResponse.
- FEATURE: системные настройки: префикс маршрутов, TTL токена, пагинация, лимит запросов, доверенные прокси, список провайдеров, журналирование, CORS.
- FEATURE: провайдеры эндпоинтов — сторонний пакет добавляет свои маршруты через настройку mxapi.providers или событие mxApiOnRegisterEndpoints, не правя mxApi. Сломанный провайдер не роняет остальной API.
- FEATURE: ProcessorEndpoint — эндпоинт поверх процессора MODX с жёстким allow-list входных свойств, приведением типов, пагинацией и разворотом списочного ответа в data + meta.total.
- FIX: ответ процессора считается успешным по ключу success. modProcessorResponse::isError() проверяет его только для массива, а списочные процессоры отдают JSON-строку — их ошибки прошли бы как HTTP 200.
- FEATURE: описание реализации (процессор, маппинг полей) видно в админке, но не отдаётся во внешний каталог и OpenAPI.
- DOCS: описание того, как пакету или сайту добавить свои эндпоинты.
- FEATURE: конвейер промежуточных обработчиков; проектные обработчики подключаются настройкой mxapi.middleware.
- FEATURE: ограничение частоты запросов — окно в минуту, лимит на клиента (поле rate_limit) или общий из настройки, заголовки X-RateLimit-*, ответ 429 rate_limited.
- FEATURE: идемпотентность изменяющих запросов по заголовку Idempotency-Key: повтор возвращает ответ первого успешного вызова с заголовком Idempotency-Replayed, операция заново не выполняется.
- FEATURE: автоматическая уборка протухших токенов и старых записей журнала не чаще раза в час — крон не требуется.
- FEATURE: CMP «Компоненты → mxApi» — каталог эндпоинтов только на чтение: маршрут, методы, scope, право, параметры, пример curl, источник (ядро/пакет/проект) и кнопка выгрузки OpenAPI.
- FEATURE: GET /meta/openapi — спецификация OpenAPI 3.0, собранная из живого реестра эндпоинтов; служебные эндпоинты и детали реализации в неё не попадают.
- CHANGE: публичный префикс маршрутов по умолчанию — /mxapi/v1 (решение владельца 30.07.2026; в черновиках роадмапа значился /api/mx/v1).




Последние обсуждения в сообществе MODX.pro