framework_eng/skills/bsl-practices/integration-patterns/SKILL.md
1C integration patterns: HTTP/REST/SOAP services, authentication (Basic/Token/OAuth/CertificateAuth), idempotency, retry, secure secret storage, versioning. Use when you need to create an HTTP service, a REST/SOAP client, implement a webhook, define a contract, configure authentication, or handle errors from external interactions.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework integration-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Key principle: The contract is defined before the code. An HTTP service is a thin layer for parsing the request and forming the response; business logic lives in common modules. Secrets belong only in БезопасноеХранилище, never in code or constants.
Before writing code, fix the contract as a structure or comment:
Changing the contract without versioning breaks compatibility. Add new fields without removing old ones. Change the semantics of existing fields through a new version (/v2/…).
An HTTP service handler should do only three things: parse the request, call the business function, return the response. Do not put business logic directly into the handler.
// Обработчик метода POST ресурса /orders
Функция ОбработатьПОСТ(Запрос)
// 1. Разбор тела
ТелоСтрокой = Запрос.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоСтрокой);
ПараметрыЗаказа = ПрочитатьJSON(ЧтениеJSON, Тип("Структура"));
// 2. Валидация обязательных полей
Если НЕ ЗначениеЗаполнено(ПараметрыЗаказа.НомерВнешнего) Тогда
Возврат ОтветОшибки(400, "VALIDATION_ERROR",
НСтр("ru = 'Поле НомерВнешнего обязательно'"));
КонецЕсли;
// 3. Бизнес-вызов
Попытка
РезультатСозданияЗаказа = ИнтеграцияЗаказов.СоздатьЗаказ(ПараметрыЗаказа);
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ИнтеграцияЗаказов.СоздатьЗаказ'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат ОтветОшибки(500, "INTERNAL_ERROR",
НСтр("ru = 'Внутренняя ошибка сервера'"));
КонецПопытки;
// 4. Формирование ответа
Возврат ОтветУспеха(201, РезультатСозданияЗаказа);
КонецФункции
// Вспомогательные функции формирования ответа
Функция ОтветОшибки(КодСостояния, КодОшибки, Сообщение)
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
СтруктураОшибки = Новый Структура("error, message", КодОшибки, Сообщение);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураОшибки);
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Возврат Ответ;
КонецФункции
Функция ОтветУспеха(КодСостояния, Данные)
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Данные);
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Возврат Ответ;
КонецФункции
All secrets (tokens, passwords, keys) are stored exclusively in БезопасноеХранилище. Never in configuration constants, session parameters, directory attributes, or module text.
For details on each scheme, see references/auth-schemes.md.
// Получение учётных данных из безопасного хранилища
УчётныеДанные = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом");
Логин = УчётныеДанные.Логин;
Пароль = УчётныеДанные.Пароль;
// Не логируйте: Логин/Пароль, Заголовок Authorization, тело с персданными
Соединение = Новый HTTPСоединение(
"api.example.com",
, // порт — по умолчанию 443 для HTTPS
Логин,
Пароль,
, // прокси
30, // таймаут (сек)
Новый ЗащищённоеСоединение); // OpenSSL / CertificateAuth — см. ниже
// Токен из безопасного хранилища
ТокенДоступа = БезопасноеХранилище.Прочитать("ИнтеграцияСВнешнимСервисом").ТокенДоступа;
Запрос = Новый HTTPЗапрос("/api/v1/resource");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Путь к сертификату и пароль — из безопасного хранилища
ПараметрыCertAuth = БезопасноеХранилище.Прочитать("ИнтеграцияСертификат");
СертификатКлиента = Новый СертификатКлиентаФайл(
ПараметрыCertAuth.ПутьКСертификату,
ПараметрыCertAuth.Пароль);
ЗащИтоеСоединение = Новый ЗащищённоеСоединение(
, , СертификатКлиента, ,
Истина); // проверять серверный сертификат
Соединение = Новый HTTPСоединение(
"api.example.com", , , , , 30, ЗащищённоеСоединение);
External calls are unreliable. Always wrap them in Попытка/Исключение. For mutating operations, use an idempotency key and protection against repeated execution.
Функция ВызватьВнешнийAPIСПовтором(URLПуть, ТелоЗапросаJSON, КлючИдемпотентности = "")
МаксимумПопыток = 3;
ОжиданиеМеждуПопытками = 2; // секунды
Для НомерПопытки = 1 По МаксимумПопыток Цикл
Попытка
Соединение = Новый HTTPСоединение(
"api.example.com", , , , , 30,
Новый ЗащищённоеСоединение);
Запрос = Новый HTTPЗапрос(URLПуть);
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Ключ идемпотентности — безопасное повторение без дублирования
Если ЗначениеЗаполнено(КлючИдемпотентности) Тогда
Запрос.Заголовки.Вставить("Idempotency-Key", КлючИдемпотентности);
КонецЕсли;
Запрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON, КодировкаТекста.UTF8);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300 Тогда
Возврат Ответ.ПолучитьТелоКакСтроку();
ИначеЕсли Ответ.КодСостояния >= 400 И Ответ.КодСостояния < 500 Тогда
// Клиентская ошибка — не ретраить
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Ошибка запроса (HTTP %1). Повтор нецелесообразен.'"),
Ответ.КодСостояния);
ИначеЕсли Ответ.КодСостояния >= 500 Тогда
// Серверная ошибка — ретраим
ВызватьИсключение СтрШаблон(
НСтр("ru = 'Сервер вернул %1'"), Ответ.КодСостояния);
КонецЕсли;
Исключение
ИнфоОшибки = ИнформацияОбОшибке();
УровеньЖР = ?(НомерПопытки < МаксимумПопыток,
УровеньЖурналаРегистрации.Предупреждение,
УровеньЖурналаРегистрации.Ошибка);
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ВнешняяИнтеграция.HTTPЗапрос'"), УровеньЖР, , ,
СтрШаблон(НСтр("ru = 'Попытка %1/%2. Ошибка: %3'"),
НомерПопытки, МаксимумПопыток,
ПодробноеПредставлениеОшибки(ИнфоОшибки)));
Если НомерПопытки = МаксимумПопыток Тогда
ВызватьИсключение;
КонецЕсли;
// Пауза перед следующей попыткой
ТекущаяДата = ТекущаяДата();
Пока ТекущаяДата() < ТекущаяДата + ОжиданиеМеждуПопытками Цикл
КонецЦикла;
КонецПопытки;
КонецЦикла;
Возврат "";
КонецФункции
Mutating operations (POST creation, state changes) must be protected against double execution.
Idempotency patterns:
Idempotency-Key in the header (UUID generated on the client side);НомерВнешнего) in the body with a unique index on the 1C side;// Проверка дубля перед записью (серверная идемпотентность)
Функция НайтиЗаказПоВнешнемуНомеру(НомерВнешнего)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Заказы.Ссылка КАК Ссылка
|ИЗ
| Документ.ЗаказКлиента КАК Заказы
|ГДЕ
| Заказы.НомерВнешнего = &НомерВнешнего";
Запрос.УстановитьПараметр("НомерВнешнего", НомерВнешнего);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Ссылка, Неопределено);
КонецФункции
// В обработчике POST:
СуществующийЗаказ = НайтиЗаказПоВнешнемуНомеру(ПараметрыЗаказа.НомерВнешнего);
Если СуществующийЗаказ <> Неопределено Тогда
// Дубль — вернуть 200 с данными существующего заказа (не 201)
Возврат ОтветУспеха(200, ПолучитьДанныеЗаказа(СуществующийЗаказ));
КонецЕсли;
The error response must be predictable. The client must be able to distinguish:
| HTTP code | Error type | Client behavior | |----------|-----------|------------------| | 400 | Input validation error | Do not retry; fix the request | | 401 | Authentication error | Refresh the token; do not retry immediately | | 409 | Conflict / duplicate | Do not retry; handle the duplicate | | 422 | Business error (data is valid, but a rule is violated) | Do not retry; show to the user | | 500 | Internal error | Retry with backoff | | 503 | Service temporarily unavailable | Retry with backoff |
// Единая структура тела ошибки
// {"error": "VALIDATION_ERROR", "message": "...", "correlationId": "..."}
Функция ОтветОшибкиС Корреляцией(КодСостояния, КодОшибки, Сообщение, КорреляцияИд)
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.Заголовки.Вставить("X-Correlation-Id", КорреляцияИд);
ТелоОшибки = Новый Структура;
ТелоОшибки.Вставить("error", КодОшибки);
ТелоОшибки.Вставить("message", Сообщение);
ТелоОшибки.Вставить("correlationId", КорреляцияИд);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ТелоОшибки);
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Возврат Ответ;
КонецФункции
Never return the 1C stack trace, module names, or internal metadata object IDs to the external API.
For SOAP services, use WSПрокси, created through WSОпределения. Pass authentication through the proxy parameters, not in the message body.
// Создание WSПрокси с аутентификацией
Функция СоздатьПроксиПлатёжногоШлюза()
УчётныеДанные = БезопасноеХранилище.Прочитать("ПлатёжныйШлюзSOAP");
WSОпределения = Новый WSОпределения(
"https://payment.example.com/service?wsdl",
УчётныеДанные.Логин,
УчётныеДанные.Пароль,
, // прокси
30); // таймаут
Прокси = WSОпределения.СоздатьWSПрокси("PaymentService", "PaymentPort");
Прокси.Пользователь = УчётныеДанные.Логин;
Прокси.Пароль = УчётныеДанные.Пароль;
Прокси.Таймаут = 30;
Возврат Прокси;
КонецФункции
// Вызов с обработкой ошибок
Попытка
ПроксиWS = СоздатьПроксиПлатёжногоШлюза();
// XDTO-объект для тела запроса
ФабрикаXDTO = ПроксиWS.ФабрикаXDTO;
ЗапросXDTO = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("http://payment.example.com/", "PayRequest"));
ЗапросXDTO.Amount = СуммаПлатежа;
ЗапросXDTO.OrderId = Строка(ИдентификаторЗаказа);
ЗапросXDTO.Currency = "RUB";
ОтветXDTO = ПроксиWS.Pay(ЗапросXDTO);
Если ОтветXDTO.Status <> "OK" Тогда
ВызватьИсключение СтрШаблон(НСтр("ru = 'Платёжный шлюз вернул: %1'"), ОтветXDTO.Status);
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ПлатёжныйШлюз.Оплата'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
correlationId, requestId, X-Correlation-Id);Authorization header (Bearer token, Basic credentials);БезопасноеХранилище;// Корреляция через запрос
КорреляцияИд = Запрос.Заголовки.Получить("X-Correlation-Id");
Если НЕ ЗначениеЗаполнено(КорреляцияИд) Тогда
КорреляцияИд = Строка(Новый УникальныйИдентификатор);
КонецЕсли;
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ИнтеграцияЗаказов.ПолучитьЗаказ'"),
УровеньЖурналаРегистрации.Информация,,,
СтрШаблон(НСтр("ru = 'correlationId=%1 result=OK orderId=%2'"),
КорреляцияИд, НомерЗаказа));
Check authentication first, before accessing business data.
// Проверка Bearer-токена во входящем запросе
Функция ПроверитьАутентификациюЗапроса(Запрос)
ЗаголовокAuth = Запрос.Заголовки.Получить("Authorization");
Если НЕ ЗначениеЗаполнено(ЗаголовокAuth) Тогда
Возврат Ложь;
КонецЕсли;
Если НЕ СтрНачинаетсяС(ЗаголовокAuth, "Bearer ") Тогда
Возврат Ложь;
КонецЕсли;
ПолученныйТокен = Сред(ЗаголовокAuth, 8); // убираем "Bearer "
ОжидаемыйТокен = БезопасноеХранилище.Прочитать("ВходящийAPIТокен").Токен;
// Сравнение в постоянное время (защита от timing attack)
// Для простых случаев допустимо прямое сравнение строк
Возврат (ПолученныйТокен = ОжидаемыйТокен);
КонецФункции
// В начале обработчика:
Если НЕ ПроверитьАутентификациюЗапроса(Запрос) Тогда
Возврат ОтветОшибки(401, "UNAUTHORIZED", НСтр("ru = 'Аутентификация не прошла'"));
КонецЕсли;
Changes to the contract without backward compatibility require a new version.
| Change | Compatibility | Action |
|-----------|--------------|---------|
| Add a new field to the response | Compatible | Add; document |
| Add an optional field to the request | Compatible | Add with defaults |
| Remove or rename a field | Incompatible | Create /v2/…, keep /v1/… |
| Change a field type | Incompatible | Create /v2/…, keep /v1/… |
| Change the semantics of an existing field | Incompatible | Create /v2/… |
Specify the version in the URL: /api/v1/orders, /api/v2/orders.
| Mistake | Consequence | How to avoid |
|--------|------------|--------------|
| Secret in a configuration constant or attribute | Leak during configuration / database export | Only БезопасноеХранилище |
| HTTP call inside a transaction | Timeout (30 s) = lock on all related records | Move HTTP calls outside the transaction |
| No idempotency key on retries | Duplicate data after a network error | Use Idempotency-Key or an external ID with a unique index |
| Returning the 1C stack trace in the error body | Exposure of the internal system structure | Return only a stable error code and message |
| Retry on 4xx errors | Useless load, possible repetition of the conflict | Retry only 5xx and network errors |
| Logging tokens/passwords | Secrets in the registration log | Mask before writing to the registration log |
| Business logic in the HTTP service handler | No reuse and no testing | Thin handler + separate common module |
| No input validation | Incorrect data written to the database | Check all required fields before the business operation |
| Trigger | Action | |---------|----------| | An incoming 1C HTTP service is being created | Apply rules 2, 6, 9, 10 | | An HTTP client is being created (outgoing REST) | Apply rules 3, 4, 5, 8 | | SOAP / Web Service is being configured | Apply rule 7, 3 (secrets) | | A contract is being designed or changed | Start with rule 1, apply rule 10 | | Any authentication is being added | Apply rule 3 + references/auth-schemes.md | | Reviewing integration code | Go through the checklist below |
БезопасноеХранилище; nothing is logged anywherecorrelationId is logged, but not the token/password/v2/…)tools
Diagnostics for Vanessa Automation runs. Use when a feature scenario failed, artifacts were not created, or you need to classify a failure after launch.
tools
Creating and refining Vanessa Automation feature scenarios based on real project requirements. Use when you need to write or update a scenario test, not just run it.
tools
--- name: v8-session-manager description: Use when working with the 1С session manager (v8-session-manager) - launch, configuration, connecting 1С clients, reading session_list, calling proxied MCP-tools from 1С extensions, diagnostics. Triggers: mention of `v8-session-manager`, `session_list`, 1С extension MCP showcase, error “no active sessions” / “session_id required”, connecting a client to the manager via `mcpMode=ws`. provides_capabilities: # Built-in manager tools — always available whi
tools
Use when Codex needs to manage v8-runner on local 1C projects through the CLI: configure v8project.yaml, initialize infobases or EDT workspaces, build sources from Designer or EDT, run syntax checks and tests, dump infobase changes, convert source formats, load or export artifacts, launch 1C clients, or choose safe 1C automation command sequences.