rest · REST API 26.650.0 Ломающее

Rest 26.650.0: тарифные ограничения Vibe+ и установка приложений через блокировку

1 мин чтения

Ломающее обновление

Удалены или изменены публичные API — прикладной код может перестать работать.

rest 26.650.0 (сборка от 06.08.2026, 78 файлов, +3003/−371 строк) почти целиком про тарифные ограничения REST для модели монетизации Bitrix24 «Vibe+». Тарифные решения принимаются по данным модуля bitrix24, и на коробке без него ограничения не включаются. Часть изменений касается всех редакций: установка приложений теперь идёт через блокировку и откат, user.get перестал делать лишние запросы, а у нескольких классов модуля поменялись контракты. Deprecated нет, методы не удалялись.

Проверьте свои реализации и наследников классов rest

Internal\Contract\Repository\IncomingWebhookRepositoryInterface получил метод exists(WebhookFilter $filter): bool. Своя реализация интерфейса без него не загрузится. Если отдельного запроса «есть ли хоть одна запись» у вас нет, хватит такого:

        <?php declare(strict_types=1);

namespace Vendor\Rest\Repository;

use Bitrix\Rest\Internal\Contract\Repository\IncomingWebhookRepositoryInterface;
use Bitrix\Rest\Internal\Repository\IncomingWebhook\WebhookFilter;

final class AuditedWebhookRepository implements IncomingWebhookRepositoryInterface
{
    // ...остальные методы интерфейса

    public function exists(WebhookFilter $filter): bool
    {
        return $this->getCount($filter) > 0;
    }
}

    

У CreateIncomingWebhookCommandHandler появился собственный конструктор: репозиторий вебхуков, репозиторий интеграций и ?TariffAccessService. Раньше конструктор наследовался от AbstractCreateIncomingWebhookCommandHandler, и третьим параметром был SecurityAuditLogger. Код, который передавал логгер третьим аргументом, получит TypeError. Подменить логгер в этом обработчике теперь нельзя, в родителя уходят только два репозитория.

Остальные изменения сигнатур опасны для наследников. Классы не final, и переопределение со старой сигнатурой станет несовместимым:

  • Engine\Access::isFeatureEnabled() получил тип возврата : bool;
  • Engine\Access::isAvailableCount() получил параметр bool $force = false;
  • Marketplace\Client::isSubscriptionAccess() получил тип возврата : bool;
  • Api\User::getUserData() получил параметр ?array $userFieldMetadata = null;
  • Preset\Provider::saveIntegration() получил параметр bool $skipTariffCheck = false;
  • AbstractCreateIncomingWebhookCommandHandler::createIntegration() и ApplicationInstaller::installAsPersonal() получили такой же bool $skipTariffCheck = false;
  • Internal\Repository\Application\AppRepository::getCount() получил параметр int $cacheTtl = 0;
  • Infrastructure\Rest\Controller\Portal\License::getAction() получил третий обязательный параметр Entity\Portal\RestAvailability $restAvailability.

Установка приложений идёт через блокировку и откат

Новый ApplicationInstallationFinalizer теперь отвечает за момент, когда приложение становится установленным. Он переключает соединение на мастер (useMasterOnly(true)), берёт именованную блокировку БД rest_market_application_installation с таймаутом 5 секунд, заново считает лимит приложений (Access::isAvailableCount(..., force: true)) и только потом ставит INSTALLED и ACTIVE. Если лимит превышен, приложение деактивируется и удаляется.

Установка из маркета (Marketplace\Application и соответствующая ветка в CRestUtil) теперь сначала сохраняет ещё не установленное приложение как неактивное и неустановленное, потом вызывает финализатор. Если он отказал, модуль просит OAuth-сервис удалить приложение (unInstallApplication), ошибки этого вызова глушатся.

Через финализатор теперь идут и действие set_installed в компоненте app.layout, и импорт конфигурации. Раньше AppTable::install() и запись в журнал установки выполнялись, даже если обновление записи не удалось. Теперь только при успехе, а импорт добавляет в уведомления ошибку с кодом.

Коды ошибок финализатора:

  • APPLICATION_INSTALLATION_LOCK_NOT_ACQUIRED — блокировку не удалось взять за 5 секунд;
  • MARKET_APPLICATION_LIMIT_EXCEEDED — исчерпан лимит приложений, в ответ установки добавляются helperCode и, если есть модуль market, vibePlusApplicationLimit;
  • APPLICATION_INSTALLATION_UPDATE_FAILED — не удалось обновить запись;
  • APPLICATION_INSTALLATION_ROLLBACK_FAILED — не удался откат после отказа.

Если вы массово ставите приложения скриптом, параллельные установки теперь выстраиваются в очередь на блокировке, и те, что прождали дольше 5 секунд, получат APPLICATION_INSTALLATION_LOCK_NOT_ACQUIRED.

Обработайте код FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN

Отказ в авторизации вебхука или OAuth-приложения из-за ограничений раньше всегда приходил как ACCESS_DENIED с текстом «REST is available only by subscription.» или «...only on commercial plans.». Если теперь отказ вызван тарифом Vibe+, приходит error = FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN со статусом 403, остальные отказы остались ACCESS_DENIED. Если в bitrix24 есть сервис upsell-проекции, в ответ подмешиваются ещё и его поля. В V3 то же самое оформлено исключением FeatureNotAvailableOnCurrentPlanException со статусом 403.

Интеграциям, которые ходят в облачные порталы, стоит отличать этот код от обычного отказа в доступе:

        <?php declare(strict_types=1);

namespace Vendor\Sync\Portal;

use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json;

final class WebhookClient
{
    public function __construct(private readonly string $webhookUrl) {}

    public function call(string $method, array $params = []): mixed
    {
        $http = new HttpClient(['socketTimeout' => 10, 'streamTimeout' => 30]);
        $raw = $http->post(rtrim($this->webhookUrl, '/') . '/' . $method . '.json', $params);
        $response = Json::decode((string)$raw);

        return match ($response['error'] ?? null) {
            null => $response['result'] ?? null,
            'FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN' => throw new PlanRestrictedException(
                (string)($response['error_description'] ?? ''),
            ),
            default => throw new \RuntimeException(
                $response['error'] . ': ' . ($response['error_description'] ?? ''),
            ),
        };
    }
}

    

PlanRestrictedException здесь ваш класс. Такую ошибку нет смысла ретраить, её надо показать администратору портала.

Внутри портала то же ограничение проявляется исключением. Preset\Provider::saveIntegration() в начале, до сохранения, зовёт TariffAccessService::ensurePresetAvailable() и может бросить Internal\Exception\VibePlus\FeatureNotAvailableOnCurrentPlanException. Раньше метод сообщал об ошибках только массивом с status и errors. Та же проверка стоит в InstallLocalAppCommandHandler, InstallPersonalAppCommandHandler, CreateIncomingWebhookCommandHandler и в компоненте rest.hook.ap.edit перед созданием вебхука. Если вы вызываете это из своего кода, ловите FeatureNotAvailableOnCurrentPlanExceptionInterface. У saveIntegration(), ApplicationInstaller::installAsPersonal() и createIntegration() появился параметр skipTariffCheck, и true в него передают только «принудительные» обработчики ForceInstallPersonalAppCommandHandler и ForceCreateIncomingWebhookCommandHandler.

Исходящие события могут не дойти

Когда тариф запрещает пользовательские вебхуки, Event\Sender молча пропускает часть обработчиков событий. Продолжают получать события опубликованные приложения (статус не STATUS_LOCAL) и системные обработчики, у которых нет ни APP_ID, ни INTEGRATION_ID, ни USER_ID. Пропускаются локальные приложения, пользовательские интеграции и обработчики с USER_ID. Отдельно отлавливаются старые боты: обработчик ONIMBOT*, чей токен через Im\Model\BotTable ведёт к интеграции с USER_ID > 0, считается пользовательским. Для этого Event\Callback теперь выбирает ещё INTEGRATION_ID и APP_STATUS.

Ошибки при этом нет, событие не отправляется. Если в облаке у клиента перестали приходить события в локальное приложение, начните с тарифа.

Как устроены ограничения Vibe+

Решения принимает новый Internal\Service\VibePlus\TariffAccessService, он доступен через ServiceContainer::getVibePlusTariffAccessService(). Он читает фичи тарифа rest_access и rest_access_transition_period и переменную market_application_limit, а состояние запуска Vibe+ (дата старта, переходный период, текущая редакция) берёт у RuntimeStateProvider из модуля bitrix24. Если модуля нет, зависимости сервиса пустые: getRestAccessAvailability() возвращает null, методы ensure*() ничего не бросают, лимит приложений не применяется. Если модуль есть, а провайдера состояния нет, в лог rest.vibe_plus.tariff_access один раз уходит warning «tariff restrictions are not applied».

Когда лимит маркет-приложений применим, считают по-новому. Раньше в выборку попадали все активные приложения, теперь установленные со статусами маркета (FREE, PAID, DEMO, TRIAL, SUBSCRIPTION). В счётчик раньше шли только бесплатные, теперь все нелокальные. Порог для ещё не установленного приложения прежний, оно блокируется при count >= limit. Поменялось поведение для уже установленного: раньше его тоже отсекали, если счётчик превышал лимит (count > limit), а при лимите Vibe+ эта проверка к нему не применяется.

Для своих экранов и отчётов есть публичные сервисы. VibePlusUsageProvider отдаёт «сырые» счётчики, а VibePlusMarketApplicationLimitProvider отдаёт проекцию лимита:

        <?php declare(strict_types=1);

use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
use Bitrix\Rest\Public\Enum\VibePlus\MarketApplicationLimitState;
use Bitrix\Rest\Public\Service\VibePlusMarketApplicationLimitProvider;

Loader::requireModule('rest');

$limit = ServiceLocator::getInstance()
    ->get(VibePlusMarketApplicationLimitProvider::class)
    ->getProjection();

if ($limit->getState() === MarketApplicationLimitState::Finite && $limit->isInstallationBlocked())
{
    $message = sprintf('Установлено %d из %d, новое приложение из маркета не встанет', $limit->getCount(), $limit->getLimit());
}

    

Состояний четыре: finite, unlimited, notApplicable (модель монетизации не Vibe+ или дата запуска ещё не наступила) и unknown (не удалось получить лимит). На коробке без bitrix24 вы получите unknown.

Рядом поменялась логика подписки маркета. Marketplace\Client::isSubscriptionAccess() при установленном bitrix24 раньше проверял регион по префиксу лицензии в списке ru, ua, by с датами старта для двух последних. Теперь условие такое: модель монетизации SUBSCRIPTION или регион лицензии ru либо by. Украина из списка выпала, константы с регионами и датами удалены. В MarketSubscription проверка региона ru заменена проверкой модели монетизации, и без модуля bitrix24 эта проверка возвращает false.

user.get стал дешевле

Формат ответа прежний, а запросов к базе стало меньше:

  • checkAllowedFields() не вызывается, а метаданные пользовательских полей не грузятся при трёх условиях: select задан явно (не пустой и без *), в нём нет UF_-полей, кроме UF_DEPARTMENT, а в сортировке и фильтре нет ни одного UF_-поля, включая UF_DEPARTMENT;
  • метаданные UF запрашиваются один раз на весь вызов, а не для каждой строки, и без LANGUAGE_ID: в комментарии сказано, что читаются только USER_TYPE_ID и MULTIPLE;
  • фильтр по явному списку ID, который помещается в первую страницу, обходится без COUNT, а для одного ID ставится LIMIT 1;
  • список пользователей экстранет-групп запрашивается только в тех ветках, где он нужен.

БД

db.diff пуст. В install/migrations/tables.php в таблице входящих вебхуков b_rest_ap появилась колонка DATE_EXPIRE типа date, а ещё добавлена новая таблица b_rest_app_install_request: код и версия приложения, USER_ID, CHECK_HASH, INSTALL_HASH, название, иконка, STATUS (по умолчанию pending), RESOLVED_BY_ID, даты создания и решения. Кода, который с ними работает, в релизе нет: ни ORM-класса для новой таблицы, ни обращений к DATE_EXPIRE. Скрипта обновления в отчёте тоже нет.

Мелочи и находки

  • Вебхуки и приложения с внешним атрибутом vibecodeconnector = Y не попадают в счётчик пользовательских, отдельно считаются ключи с developer_key = Y. Приложения с новым системным атрибутом FORCE_INSTALLED тоже исключены из подсчёта пользовательских интеграций.
  • LicenseScannerStateInvalidator::reset() дёргает Bitrix\Bitrix24\LicenseScanner\Manager::resetComputedState() после изменений приложений, вебхуков, интеграций, их атрибутов и списка бесплатных приложений. Вызов обложен class_exists, method_exists и catch (\Throwable) на случай старой версии bitrix24.
  • В rest.integration.edit/class.php добавлен use ...\AppInstallCompletionMode, который нигде не используется, а файла с таким классом в релизе нет.
  • В SystemIncomingWebhookCreator исправлена утечка состояния: EventController::enableEvents() переехал в finally. Раньше исключение при создании системного вебхука оставляло события пресетов выключенными до конца запроса.
  • Preset\Provider::saveIntegration() теперь проставляет INTEGRATION_ID обработчикам событий интеграции, и новым, и уже существующим.
  • Ответ контроллера лицензии портала (Portal\License::getAction()) получил ключ rest с полем isAvailable.
  • Подсказки об ограничениях в формах интеграций берут код из upsell-проекции bitrix24, а зашитый limit_subscription_market_access_buy_marketplus остался запасным вариантом.
  • Marketplace\Immune::load() теперь обновляет и статический список иммунных приложений, а не только кеш.

Что делать

  • Если вы реализуете IncomingWebhookRepositoryInterface, добавьте exists().
  • Если создаёте CreateIncomingWebhookCommandHandler руками с тремя аргументами, уберите третий или передайте туда TariffAccessService.
  • Если вызываете Provider::saveIntegration(), команды установки приложений или создания вебхука из своего кода, ловите FeatureNotAvailableOnCurrentPlanExceptionInterface.
  • В интеграциях с облачными порталами обработайте FEATURE_NOT_AVAILABLE_ON_CURRENT_PLAN отдельно от ACCESS_DENIED.
  • Если ставите приложения скриптом или импортом конфигурации, проверьте обработку новых кодов ошибок установки.

Читайте дальше

rest 26.600.0 Ломающее Свежее

Rest 26.600.0: отложенный batch и Idempotency-Key в REST V3, новые правила для встроек

В `rest` 26.600.0 (сборка от 23.07.2026) 143 файла и +5143/−383 строк. В REST V3 появились отложенный batch со своей таблицей и очередью, идемпотентность по заголовку `Idempotency-Key` и перечисления, значения которых берутся из кода. Заодно переделали права на встройки и сигнатуры команд встроек. К...

1 мин
rest 26.500.0 Безопасность

rest 26.500.0: журнал аудита безопасности и курсорные ответы в V3

REST завёл единый журнал того, что происходит с приложениями, правами и вебхуками, а установщик переехал с SQL-батчей на миграции ядра. Публичные REST-методы не тронуты — ломается внутреннее и V3-API, так что читать этот разбор стоит тем, кто пишет свои модули поверх `rest`, а не тем, кто дёргает `c...

3 мин
voximplant 26.700.0 Ломающее Свежее

Voximplant 26.700.0: оценку качества связи убрали, у notifyAdmins() появился тип

Модуль телефонии voximplant 26.700.0 вышел в коробочный Битрикс24 11 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Из карточки звонка убрали оценку качества связи вместе с её JS-событиями, у `Im::notifyAdmins()` появился тип параметра, а публичные константы `SipStatusInfor...

3 мин
ui 26.687.0 Рутинное Свежее

UI 26.687.0: триал VibePlus в облаке и дизайн чипа TintedBitrixGpt

`Bitrix\UI\Controller\InfoHelper` при активации демо сначала спрашивает у модуля bitrix24, включён ли старт VibePlus, и если да, запускает триал VibePlus вместо обычного демо тарифа. Ветка срабатывает только при подключённом модуле bitrix24 и определённой константе `BX24_HOST_NAME`. В `ui.system.chi...

1 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти