Создание REST API в 1С-Битрикс D7: маршруты, контроллеры и авторизация

Собственный REST API в 1С-Битрикс на D7 нужен для мобильных клиентов, витрин на отдельном фронте и интеграций. Ниже — рабочая схема: маршруты, контроллер Engine, авторизация, JSON-ответ и типичные ошибки.

Базовый разбор configureActions, prefilters, BX.ajax.runAction и ошибок CSRF/метода — в отдельном гайде Engine\Controller: AJAX, runAction и типичные ошибки. Связанные темы: Web\Json, Application/Context, HttpClient, Result / Error.

Регистрация маршрутов

В современных версиях ядра маршруты подключают через routing.php / конфиг модулей. Минимальный пример группы под префиксом api/v1:

<?php
use Bitrix\Main\Routing\RoutingConfigurator;

return function (RoutingConfigurator $routes) {
    $routes->prefix('api/v1')->group(function (RoutingConfigurator $routes) {
        $routes->get('users', [\Local\Api\UserController::class, 'getList']);
        $routes->post('users', [\Local\Api\UserController::class, 'create']);
        $routes->get('users/{id}', [\Local\Api\UserController::class, 'get']);
    });
};

Проверьте, что файл маршрутов реально подключён в проекте (модуль или /local/). Иначе получите 404 и будете чинить контроллер зря.

Контроллер Engine

Контроллер наследует Bitrix\Main\Engine\Controller. Action-методы заканчиваются на Action, prefilters закрывают метод и авторизацию:

<?php
namespace Local\Api;

use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\UserTable;

class UserController extends Controller
{
    public function configureActions(): array
    {
        return [
            'getList' => [
                'prefilters' => [
                    new ActionFilter\Authentication(),
                    new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_GET]),
                ],
            ],
            'create' => [
                'prefilters' => [
                    new ActionFilter\Authentication(),
                    new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
                    new ActionFilter\Csrf(),
                ],
            ],
        ];
    }

    public function getListAction(): array
    {
        $rows = UserTable::getList([
            'select' => ['ID', 'NAME', 'EMAIL'],
            'filter' => ['=ACTIVE' => 'Y'],
            'order'  => ['ID' => 'DESC'],
            'limit'  => 20,
        ])->fetchAll();

        return [
            'items' => $rows,
            'count' => count($rows),
        ];
    }
}

Массив, который возвращает action, ядро само отдаёт как JSON. Не делайте внутри action echo Json::encode(...); die; — получите двойную сериализацию или сломанный ответ Engine.

Имя ключа в configureActions — без суффикса Action (getList → метод getListAction). Для внутреннего AJAX админки и публички того же контроллера вызывайте BX.ajax.runAction — пошагово в статье Engine\Controller: AJAX, runAction и типичные ошибки. Маршруты api/v1 из раздела выше — уже HTTP-обёртка; сам класс Controller при этом один и тот же.

Когда нужен ручной Json::encode

Ручная сериализация нужна вне Engine: свой ajax-файл, webhook «на вход», отдача файла, ответ из старого компонента. Тогда:

<?php
use Bitrix\Main\Web\Json;
use Bitrix\Main\Application;

$response = Application::getInstance()->getContext()->getResponse();
$response->addHeader('Content-Type', 'application/json; charset=UTF-8');
$response->flush(Json::encode([
    'ok' => true,
    'id' => 42,
]));

Подробный разбор encode/decode, UTF-8 и типичных ошибок — в статье про Bitrix D7 Web\Json. Не опирайтесь на «голый» json_encode без проверки ошибок.

Авторизация: сессия, CSRF и токен API

Для браузерных запросов от авторизованного пользователя Битрикс достаточно ActionFilter\Authentication + Csrf (для меняющих методов). Для server-to-server удобнее отдельный токен в заголовке:

<?php
use Bitrix\Main\Application;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Web\Json;

$request = Application::getInstance()->getContext()->getRequest();
$token = (string)$request->getHeader('X-Api-Token');
$expected = (string)Option::get('local.api', 'token', '');

if ($expected === '' || !hash_equals($expected, $token)) {
    http_response_code(401);
    header('Content-Type: application/json; charset=UTF-8');
    echo Json::encode(['error' => 'unauthorized']);
    die();
}

Токен храните в Option/env, не в репозитории. Сравнивайте через hash_equals, не через ==.

CORS для отдельного фронтенда

Если UI на другом домене, закройте origin белым списком — не * вместе с credentials:

<?php
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allow = ['https://app.example.ru'];

if (in_array($origin, $allow, true)) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Vary: Origin');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, X-Api-Token, X-Bitrix-Csrf-Token');
}

if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
    http_response_code(204);
    exit;
}

Формат ответа и ошибки

Держите один контракт: { "items": [...], "error": null } на успех и понятный error + HTTP-код на провал. Пустой список — это [], а не «ошибка». Для интеграций наружу проверяйте и статус, и JSON через Json::decode (см. HttpClient).

Типичные ошибки

  • Маршруты не подключены — 404 при живом контроллере.
  • Двойной JSON: Engine уже сериализует return, плюс ручной echo json_encode.
  • Забыли CSRF на POST из браузера — 401/пустой ответ.
  • Токен API в query-строке и в логах access.
  • CORS * + cookies — браузер режет запросы.

Вывод

REST на D7 строится из трёх кирпичей: маршруты → Controller с фильтрами → аккуратный JSON-контракт. Детали Engine\Controller, prefilters и runAction — в Engine\Controller: AJAX, runAction и типичные ошибки. Сериализацию вне Engine делайте через Web\Json, HTTP-контекст берите из Application, исходящие вызовы — через HttpClient с таймаутами.