Внешние интеграции
Руководство для разработчиков и интеграторов: как подключать ERP, 1C, Service Desk, BI и другие системы к Portal.
Старт разработки и карта разделов: Разработчикам. Практические гайды: узлы, списки.
См. также: REST API, кейсы кода, OpenAPI.
Главный вывод
Заголовок раздела «Главный вывод»Свои маршруты вида /api/v1/my-erp/sync пакет не регистрирует. Платформа публикует фиксированный REST (/api/v1/*) и одну точку для пользовательской логики: POST /api/v1/web-services/call/{id} — пакет .portalapi. {id} берётся из manifest.json (vendor.name). Тело запроса — JSON, ответ — JSON в конверте { success, data }.
Вызов идёт от вошедшего пользователя. Списки внутри ручки читаются с его ACL. Лимит ожидания — query timeout в секундах (по умолчанию 30, максимум 600). Если время вышло, ответ 408 и error.code = TIMEOUT, без HTTP 500.
Внешняя система ──JWT + CRUD──► /api/v1 REST APIВнешняя система / WebPart ──JWT + JSON──► POST /api/v1/web-services/call/{id}WebPart UI ──invoke/action──► /api/v1/webparts/invoke/{key}/…Event Receiver / Timer Job ──HTTP из C#──► внешняя системаВажно:
/api/v1/routes— разрешение URL-адресов страниц для браузера, а не API для интеграций.
Путь 1. Внешняя система вызывает Portal (основной сценарий)
Заголовок раздела «Путь 1. Внешняя система вызывает Portal (основной сценарий)»Внешняя система — клиент встроенного REST API.
- Аутентификация — сервисная учётная запись,
POST /api/v1/auth/login→ JWT (аутентификация) - Обнаружение ресурсов —
GET /api/v1/nodes,GET /api/v1/lists/{id}/fields - Чтение/запись данных —
GET/POST/PATCH /api/v1/lists/{listId}/items - Права — при необходимости
GET/POST /api/v1/permissions(права)
Паттерн записи данных
Заголовок раздела «Паттерн записи данных»- Фильтрация — по
internal_nameполя (inn,status, …) - Запись — по UUID полей из
GET .../fields(кешировать)
Пример (ERP → Portal, кейс 10):
GET /api/v1/lists/{listId}/fieldsAuthorization: Bearer …
GET /api/v1/lists/{listId}/items?filter={"logic":"and","conditions":[{"column":"inn","operator":"eq","value":"7707083893"}]}&limit=1
POST /api/v1/lists/{listId}/itemsContent-Type: application/json
{ "fieldValues": { "uuid-title": "ООО Ромашка", "uuid-inn": "7707083893", "uuid-status": "Активен" }}
PATCH /api/v1/lists/{listId}/items/42Content-Type: application/json
{ "fieldValues": { "uuid-status": "Архив" }}Справочники
Заголовок раздела «Справочники»| Материал | Описание |
|---|---|
| REST API | Обзор всех маршрутов |
| OpenAPI | Полная спецификация |
| Значения полей | Формат fieldValues |
| PortalApiClient.cs | Пример HTTP-клиента на C# |
Путь 2. Portal вызывает внешнюю систему (исходящая интеграция)
Заголовок раздела «Путь 2. Portal вызывает внешнюю систему (исходящая интеграция)»Новый HTTP-эндпоинт на Portal не создаётся. Логика исходящих вызовов пишется в C#-расширениях.
Event Receiver (.portalevent) — реакция на события
Заголовок раздела «Event Receiver (.portalevent) — реакция на события»listItem.updated → event_receivers → portal_jobs → IEventReceiver.HandleAsync| Шаг | Действие |
|---|---|
| 1 | Скачайте Extension SDK, затем dotnet new portal-eventreceiver |
| 2 | Реализовать HandleAsync(...) — внутри вызвать внешний HTTP API |
| 3 | Собрать .portalevent, загрузить: Админка → Event Receivers |
| 4 | Привязать: POST /api/v1/event-receivers с eventName + config.listId |
| 5 | Мониторинг: Админка → Задания |
Документация: Event Receivers, привязка к событиям.
Пример привязки:
{ "eventName": "listItem.updated", "jobType": "contoso.list-change-handler", "config": { "listId": "uuid-списка" }, "isEnabled": true}Timer Job (.portaltimer) — периодическая синхронизация
Заголовок раздела «Timer Job (.portaltimer) — периодическая синхронизация»Для nightly sync (ERP → Portal или Portal → ERP):
| Шаг | Действие |
|---|---|
| 1 | dotnet new portal-timerjob |
| 2 | Реализовать ExecuteAsync с HTTP-вызовами |
| 3 | Загрузить пакет, настроить расписание в Админка → Timer Jobs |
Документация: Timer Jobs, интеграция с Worker.
Путь 3. Серверная логика через WebPart (UI + actions)
Заголовок раздела «Путь 3. Серверная логика через WebPart (UI + actions)»WebPart не добавляет новые REST-маршруты, но даёт фиксированные invoke-эндпоинты:
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api/v1/webparts/invoke/{key}/render | SSR HTML |
| POST | /api/v1/webparts/invoke/{key}/action | Серверные действия |
{key} — из manifest.json пакета .portalpart, не произвольный URL.
| Шаг | Действие |
|---|---|
| 1 | dotnet new portal-webpart -n my-widget |
| 2 | RenderAsync + HandleActionAsync в IWebPart |
| 3 | Сборка .portalpart, загрузка: Админка → WebPart |
| 4 | Размещение на странице через zonesContent |
Документация: сборка WebPart, UI и API, API каталога.
Путь 4. Функциональный модуль (.portalmod) — полное решение
Заголовок раздела «Путь 4. Функциональный модуль (.portalmod) — полное решение»Если интеграция — часть готового решения (структура портала + расширения):
| Шаг | Действие |
|---|---|
| 1 | Описать module.json (узлы, списки, страницы, права) |
| 2 | Положить .portalpart/.portalevent/.portaltimer в packages/ |
| 3 | Опционально — C# provisioner для начальных данных |
| 4 | Установить: Админка → Модули или POST /api/v1/modules/install |
| 5 | Получить resource_map: GET /api/v1/modules/{key}/resources |
Документация: функциональные модули, API модулей, схема module.json.
В packages/ модуль также кладёт .portalapi, а стартовые настройки ручки — в webServices.
Путь 5. Своя JSON-ручка (.portalapi)
Заголовок раздела «Путь 5. Своя JSON-ручка (.portalapi)»Когда внешней системе или WebPart нужна операция, которой нет в CRUD списков: расчёт, сборка ответа из нескольких списков, проверка перед записью.
| Шаг | Действие |
|---|---|
| 1 | dotnet new portal-webservice, класс IWebService |
| 2 | В manifest.json задать id (vendor.name) |
| 3 | Собрать ZIP: pack-portalapi.sh |
| 4 | Админка → Веб-сервисы или пакет модуля type: web-service |
| 5 | POST /api/v1/web-services/call/{id} с JWT пользователя и JSON-телом |
Один пакет — один POST. Параметры вызова — тело. Id списка и прочие настройки администратора — config ручки, не тело. Query timeout — секунды ожидания (30 по умолчанию, до 600). Истечение лимита — HTTP 408 и { "success": false, "error": { "code": "TIMEOUT", "message": "…" } }.
Списки внутри обработчика читаются от вызвавшего пользователя. Анонимного вызова и API-ключа нет: у внешней системы должна быть учётная запись портала и JWT после POST /api/v1/auth/login.
Документация: веб-сервисы, SDK, выполнение.
Контексты выполнения кода
Заголовок раздела «Контексты выполнения кода»| Контекст | SDK | Ключи при записи | Права |
|---|---|---|---|
| REST (внешняя система) | HTTP | UUID или internal_name | JWT сервисного пользователя |
| WebPart | Portal.WebPart.Sdk | PortalListItem / internal_name | Текущий пользователь страницы |
| Event Receiver | Portal.EventReceiver.Sdk | PortalListItem / internal_name | Пользователь события |
| Timer Job | Portal.TimerJob.Sdk | PortalListItem / internal_name | системная учётка portal-system |
| Веб-сервис | Portal.WebService.Sdk | PortalListItem / internal_name | Пользователь, чей JWT на POST /web-services/call/{id} |
| Provisioner | Portal.Module.Sdk | internal_name | Системные (установка модуля) |
Подробнее: кейсы работы с сущностями.
Чего Portal не предоставляет
Заголовок раздела «Чего Portal не предоставляет»- Нет регистрации произвольного пути
/api/v1/my-erp/syncиз пакета. Своя логика — один адресPOST /api/v1/web-services/call/{id}(веб-сервисы) - Нет анонимных webhook. Внешняя система вызывает ручку или REST только с JWT пользователя портала
- Нет API keys — только JWT через логин пользователя (local или LDAP)
- Новый отдельный маршрут в платформе — изменение исходников
Portal.Api(работа вендора). Пакету достаточноidв манифесте.portalapi
Рекомендуемый порядок чтения
Заголовок раздела «Рекомендуемый порядок чтения»- REST API — обзор маршрутов
- Аутентификация — вход и JWT
- Значения полей — чтение/запись данных
- Кейсы кода — практические примеры (в т.ч. ERP)
- По типу расширения: web-services / event-receivers / timer-jobs / webparts / modules