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

Rest 26.1000.0–26.1100.0: JSON-ответы потоком, свои права на методы полей REST 3.0 и встройки модулей в интерфейсе

1 мин чтения Устаревших API: 14
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

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

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

В rest 26.1000.0 JSON-ответы REST пошли потоком, а модуль теперь сам решает, кому доступны методы полей REST 3.0. В 26.1100.0 встройки модулей без приложения из разбора 26.900.0 открываются в стандартном интерфейсе. До обновления проверьте свои точки входа REST, наследников CRestServer и провайдеры встроек.

Новое

Закройте методы полей REST 3.0 своей политикой (26.1000.0)

Для каждого ресурса REST 3.0 сам генерирует методы полей: *.field.list/get, *.field.custom.* и *.field.custom.enum.*. До 26.1000.0 их закрывал только scope. Теперь на DTO ресурса вешается #[FieldAccess(Provider::class)], и провайдер решает, пропустить ли вызов. Он реализует FieldAccessProvider::decide(FieldAccessContext $context): Result. В контексте есть userId, модуль, URI метода, действие (list, get, add, update, delete), entityId пользовательских полей и аргументы экшена. Ошибка в Result превращается в AccessDeniedException, а вызов без авторизованного пользователя до провайдера не доходит.

Провайдер создаётся через new без аргументов, зависимости берите внутри.

        <?php declare(strict_types=1);

namespace Vendor\Kb\Infrastructure\Rest\Access;

use Bitrix\Main\Error;
use Bitrix\Main\Result;
use Bitrix\Main\UserGroupTable;
use Bitrix\Rest\V3\Access\FieldAccessContext;
use Bitrix\Rest\V3\Access\FieldAccessProvider;

final class ArticleFieldAccess implements FieldAccessProvider
{
    private const ADMIN_GROUP_ID = 1;

    public function decide(FieldAccessContext $context): Result
    {
        $result = new Result();

        if (in_array($context->action, ['list', 'get'], true) || $this->isAdmin($context->userId)) {
            return $result;
        }

        return $result->addError(new Error('Поля статей меняют только администраторы', 'VENDOR_KB_FIELDS_READONLY'));
    }

    private function isAdmin(int $userId): bool
    {
        return UserGroupTable::getCount(['=USER_ID' => $userId, '=GROUP_ID' => self::ADMIN_GROUP_ID]) > 0;
    }
}

    
        use Bitrix\Rest\V3\Attribute\FieldAccess;
use Bitrix\Rest\V3\Dto\Dto;
use Vendor\Kb\Infrastructure\Rest\Access\ArticleFieldAccess;

#[FieldAccess(ArticleFieldAccess::class)]
class ArticleDto extends Dto
{
    public int $id;
    public string $title;
}

    

Если модуль уже объявил свой UserFieldAccess в секции userField файла .settings.php, подойдёт готовый UserFieldAccessProvider. Модулю без такого ACL он отказывает с кодом USER_FIELD_ACCESS_NOT_DECLARED. Ресурс без атрибута по-прежнему проверяется только по scope.

Считайте приложения одним вызовом (26.1000.0)

ApplicationProvider получил getCount(), getCodes() и hasAny(). Все три принимают ApplicationFilter: только установленные, внешние атрибуты приложения («код → значение»), источник ApplicationOrigin::Local или Marketplace и excludeImmune, который убирает приложения из списка Immune Маркета. getCodes() выбирает коды пачками по 500 и убирает дубли, getCount() кэширует ответ на cacheTtl секунд.

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;
use Bitrix\Rest\Public\Provider\ApplicationProvider;
use Bitrix\Rest\Public\Provider\Params\ApplicationFilter;
use Bitrix\Rest\Public\Provider\Params\ApplicationOrigin;

Loader::requireModule('rest');

$apps = new ApplicationProvider();
$market = new ApplicationFilter(installed: true, origin: ApplicationOrigin::Marketplace, excludeImmune: true);

if ($apps->hasAny($market)) {
    $count = $apps->getCount($market, cacheTtl: 86400);
    $codes = $apps->getCodes($market);
}

    

Замерьте время REST-методов (26.1100.0)

После каждого запроса LoadLimiter пишет в PSR-логгер rest.LoadLimiter.time строку на пару «вебхук или приложение + метод»: домен, число вызовов, полную длительность и время, которое учёл лимитер. Считаются только вызовы через входящие вебхуки и OAuth-приложения. Пока логгера нет в секции loggers файла bitrix/.settings.php, в лог ничего не попадает. Пример секции:

        'loggers' => [
    'value' => [
        'rest.LoadLimiter.time' => [
            'className' => \Bitrix\Main\Diag\FileLogger::class,
            'constructorParams' => ['/var/log/bitrix/rest-time.log', 10 * 1024 * 1024],
        ],
    ],
    'readonly' => true,
],

    

Нужен включённый лимитер. В облаке он работает всегда, на коробке его включает опция rest / load_limiter_active = Y. Та же опция включает и сам лимит в 420 секунд работы метода за 10 минут на вебхук или приложение (на коробке порог задаёт load_limiter_second_limit). На коробке время между запросами копится в Redis из опции load_limiter_connection_name. Без него лимит фактически не срабатывает, а лог пишется всё равно.

Откройте встройку модуля в интерфейсе портала (26.1100.0)

В 26.900.0 модуль мог привязать встройку к своей сущности, но показывать её приходилось самому. Теперь часть работы берёт rest:

  • EmbeddingProvider::resolveOpen($embedding, $userId) проверяет доступ пользователя и спрашивает у провайдера владельца, что открыть. Нет доступа или провайдера — вернётся null.
  • EmbeddingMenuProvider::getLeftMenuItems($placement) отдаёт пункты левого меню вместе со встройками модулей, getBindingItems($placements) отдаёт пункты меню привязок, там только приложения.
  • У EmbeddingOpenDto появился method: HttpMethod::Get кладёт payload в query, HttpMethod::Post отправляет его формой.
  • После привязки встройки модуля приходит событие onAfterPlacementAdd::<PLACEMENT>, как у приложений.

С флагом LegacyNonAppEmbeddings (механизм Main\Config\Feature, по умолчанию выключен) встройки модулей попадают в PlacementTable::getHandlersList(), а bitrix:app.layout открывает их по отрицательному ID. Iframe грузится с хоста портала, и часть JS SDK встройке закрыта: setTitle, openApplication, refreshAuth, setUserOption, setAppOption, setInstall, setInstallFinish, showAppForm.

Провайдер из прошлого разбора теперь выглядит так. Handler в BindEmbeddingCommand остаётся абсолютным http(s)-URL, а открывать нужно путь на портале:

        <?php declare(strict_types=1);

namespace Vendor\Kb\Integration\Rest;

use Bitrix\Rest\Public\Dto\Embedding\EmbeddingOwnerDto;
use Bitrix\Rest\Public\Enum\HttpMethod;
use Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingDto;
use Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingOpenDto;
use Bitrix\Rest\Public\Provider\EmbeddingOwnerProviderInterface;

final class ArticleEmbeddingOwner implements EmbeddingOwnerProviderInterface
{
    public function getEmbeddingAccessCodes(EmbeddingOwnerDto $owner): array
    {
        return ['AU'];
    }

    public function resolveEmbeddings(array $embeddings, int $userId): array
    {
        return array_map(
            static fn(EmbeddingDto $embedding): EmbeddingOpenDto => new EmbeddingOpenDto(
                embeddingId: $embedding->id,
                url: '/local/kb/embed.php',
                payload: ['ARTICLE_ID' => (int)$embedding->entityId],
                method: HttpMethod::Post,
            ),
            $embeddings,
        );
    }
}

    

Подберите порог потокового ответа (26.1000.0)

Опция rest / streamed_json_commit_threshold задаёт, сколько байт тела копить до первой отправки клиенту. По умолчанию 262144, то есть 256 КБ. Если кодирование JSON упало до порога, клиент получит обычную ошибку WRONG_ENCODING. После первой отправки ответ уже не заменить: поток оборвётся, а в лог через AddMessage2Log уйдёт запись «Rest streamed response».

Чем выше порог, тем больше памяти держит запрос и тем реже клиент видит оборванный ответ.

Что сломается

Проверьте свою точку входа REST (26.1000.0)

CRestServer::output() возвращает Main\HttpResponse|string|null вместо BFile|string|null, а protected outputJson() отдаёт StreamedJsonResponse вместо строки.

  • Наследник CRestServer с outputJson(): string упадёт на несовместимой сигнатуре.
  • echo $server->output($result) в своей точке входа падает: у HttpResponse нет __toString(). Делайте как штатный rest.server: для HttpResponse вызовите sendHeadersAdditional() и send(), для строки оставьте sendHeaders() и echo. Строку для внутреннего клиента соберёт getContent().
  • send() сбрасывает все буферы вывода и сам завершает запрос, до CMain::FinalActions() дело не доходит. Обработчик, который правил тело REST-ответа через буфер или эпилог, его больше не увидит. Заголовки и куки из ответа контекста переносятся.
  • Content-Length у JSON-ответов нет. Проверьте прокси и клиентов, которые на него рассчитывают.

Уберите Vibe+ из своего кода (26.1000.0)

Удалены публичные классы Vibe+ из 26.650.0: Public\Service\VibePlusUsageProvider, VibePlusMarketApplicationLimitProvider, Public\ValueObject\VibePlusUsageSnapshot, VibePlusMarketApplicationLimit и enum Public\Enum\VibePlus\MarketApplicationLimitState. Тариф и лимит приложений теперь считают модули bitrix24 и market, прямой замены в rest нет. Подсчёт приложений Маркета переведите на ApplicationProvider (пример выше), а hasUserRestUsage() на IntegrationProvider::hasUserIntegrations(['vibecodeconnector' => 'Y']). Без аргумента метод учтёт и интеграции приложений с внешним атрибутом vibecodeconnector = Y, которые старый метод пропускал.

Вместе с ними ушли:

  • 5-й параметр $skipTariffCheck у Preset\Provider::saveIntegration(), вызов с именованным skipTariffCheck: упадёт;
  • $skipTariffCheck у protected AbstractCreateIncomingWebhookCommandHandler::createIntegration(). На его место встал $dateExpire, и позиционный вызов из наследника получит TypeError;
  • свойство excludeAttributes у Public\Provider\Params\IncomingWebhookFilter.

Обновите провайдеры встроек (26.1100.0)

  • EmbeddingViewDto из EmbeddingProvider::getListByPlacement() вместо url и payload несёт EmbeddingOpenDto $open. Читайте $view->open->url и $view->open->payload.
  • Конструктор EmbeddingOpenDto бросает InvalidArgumentException, если url не начинается с одного / (абсолютный адрес, //, \, управляющие символы и пробелы) или payload не плоская карта «строка → скаляр». Провайдер с абсолютным handler в url, как в примере из 26.900.0, теперь бросает это исключение. getListByPlacement() его ловит: встройки такого владельца пропадают из списка, а ошибка уходит в лог. app.layout вместо встройки показывает «Не удалось открыть встройку», а прямой вызов resolveOpen() получит исключение.
  • BindEmbeddingCommand не привяжет встройку модуля к плейсментам только для приложений: USERFIELD_TYPE, PAGE_BACKGROUND_WORKER, BI_ANALYTICS_MENU, CONTACT_CENTER. run() бросит Main\Command\Exception\CommandException, а в getPrevious() будет Rest\Exceptions\ArgumentException «The placement does not support module embeddings».
  • С включённым LegacyNonAppEmbeddings getHandlersList() отдаёт встройки модулей строками с APP_ID = -<ID встройки>, INSTALLED = Y и пустым APP_ACCESS, а getAccessibleHandlerIds() добавляет их ID к ID встроек приложений. Если ваш код идёт с APP_ID из списка обработчиков в AppTable, проверяйте APP_ID > 0.
  • embeddingListAction контроллера Bitrix\Rest\Controller\Application отдаёт не-администратору пустой список, если тот спросил встройки чужого userId. Раньше так ограничивали только внешних пользователей.

Пересмотрите запросы к REST 3.0 (26.1000.0)

  • Тело запроса больше не перекрывает параметры, зашитые в маршрут. У *.field.custom.* и *.field.custom.enum.* маршрут несёт entityId, и подменить телом сущность пользовательских полей больше нельзя.
  • *.field.custom.enum.get/update/delete на значение списка чужой сущности отвечают EntityNotFoundException, как на несуществующее. Раньше get и update этого не проверяли.
  • fields в теле не массивом даёт InvalidRequestFieldTypeException.

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

  • В ответе *.field.list/get REST 3.0 появилось editableGroups, а editable считается как editableGroups !== null. Раньше маппер читал ключ editable, которого в данных нет (26.1000.0).
  • rest.scope.list и OpenAPI-документация больше не показывают выключенные методы (26.1000.0).
  • Сгенерированные DTO REST 3.0 грузятся лениво. Если серверный код берёт описание метода из SchemaManager::getMethodDescription() и сразу работает с его DTO, сначала вызовите $schemaManager->ensureGeneratedDtoClassesLoadedFromMethodDescriptions([$description]) (26.1000.0).
  • Из PlacementTable удалены protected static getPlacementCacheId() и getAccessibleHandlerCacheId(), код переехал во внутренний HandlerReader. Заденет только наследников PlacementTable (26.1100.0).

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
Bitrix\Rest\Public\ValueObject\VibePlusMarketApplicationLimit Удалено —
Bitrix\Rest\Public\Service\VibePlusUsageProvider Удалено —
Bitrix\Rest\Public\Service\VibePlusUsageProvider::getMarketApplicationCount() Удалено Bitrix\Rest\Public\Provider\ApplicationProvider::getCount()
Bitrix\Rest\Public\Service\VibePlusUsageProvider::getMarketApplicationCodes() Удалено Bitrix\Rest\Public\Provider\ApplicationProvider::getCodes()
Bitrix\Rest\Public\Service\VibePlusUsageProvider::hasVibecodeUsage() Удалено Bitrix\Rest\Public\Provider\ApplicationProvider::hasAny()
Bitrix\Rest\Public\Service\VibePlusUsageProvider::hasUserRestUsage() Удалено Bitrix\Rest\Public\Provider\IntegrationProvider::hasUserIntegrations()
Bitrix\Rest\Public\Service\VibePlusMarketApplicationLimitProvider Удалено —
Bitrix\Rest\Public\ValueObject\VibePlusUsageSnapshot Удалено —
Bitrix\Rest\Public\Enum\VibePlus\MarketApplicationLimitState Удалено —
Bitrix\Rest\PlacementTable::getPlacementCacheId() Удалено —
Bitrix\Rest\PlacementTable::getAccessibleHandlerCacheId() Удалено —
Bitrix\Rest\Public\Provider\Params\IncomingWebhookFilter::$excludeAttributes Удалено —
Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingViewDto::$url Удалено Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingViewDto::$open
Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingViewDto::$payload Удалено Bitrix\Rest\Public\Provider\Dto\Embedding\EmbeddingViewDto::$open

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

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

rest 26.900.0 Заметное Свежее

Rest 26.900.0: встройки без приложения, чтение приложений без AppTable и заготовка MCP

В rest 26.900.0 у встроек появился владелец, и модуль может повесить встройку на свою сущность без REST-приложения. Приложение читается в типизированный DTO через `ApplicationProvider::find*()`, статус оплаты считает `ApplicationStatusCalculator`, а для REST 3.0 заложен мост в MCP, который в БУС не…

2 мин
rest 26.800.0 Ломающее Свежее

Rest 26.700.0–26.800.0: отложенная установка приложений, строгий bool в REST 3.0 и вебхуки со сроком действия

В БУС вышли подряд две версии `rest`: 26.700.0 и 26.800.0. В 26.700.0 у входящих вебхуков появился срок действия, а сотрудник без права установки может попросить администратора поставить приложение. В 26.800.0 поменялось время срабатывания `OnRestAppInstall`, REST 3.0 стал строже проверять входные д…

2 мин
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.650.0 Ломающее

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

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

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

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

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

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

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

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

Войти