Собственный 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 с таймаутами.

