Типичная ситуация: 1С нужно отдать данные во внешнюю систему или, наоборот, принять запрос снаружи, а встроенный HTTP-сервис 1С для этого неудобен — нет валидации входящих данных «из коробки», сложно обрабатывать долгие операции асинхронно, тяжело логировать и покрывать тестами. В итоге логику обвязки либо городят прямо в конфигурации 1С, либо тратят часы на ручную проверку каждого входящего JSON.
В этой статье соберём рабочий микросервис на Python и FastAPI, который принимает POST-запросы от 1С, автоматически валидирует тело запроса через Pydantic, возвращает предсказуемый ответ и поднимается в Docker одной командой. Дополнительно разберём защиту эндпоинта токеном, обработку ошибок на стороне 1С и сравним подход с аналогичным сервисом на Node.js.
Что вы узнаете
- Как поднять минимальный сервис на FastAPI и подключить автоматическую валидацию через Pydantic v2
- Как вызвать этот сервис из 1С через
HTTPСоединениеи корректно обработать ошибку - Как защитить эндпоинт токеном, чтобы к нему не мог обратиться кто угодно из интернета
- Как упаковать микросервис в Docker и запустить его через docker-compose
- Чем FastAPI отличается от аналогичного решения на Node.js/Express
- Какие ошибки чаще всего допускают при интеграции 1С и Python
Зачем выносить логику из 1С в отдельный микросервис
Привет, на связи Илья Низамов. Периодически прилетают вопросы про интеграцию 1С и Python — и почти всегда за ними стоит одна из двух задач: либо нужно дёрнуть внешний API (LLM, платёжный шлюз, стороннюю CRM) из 1С, либо, наоборот, принять данные снаружи и что-то с ними сделать до того, как они попадут в базу.
Отдельная причина выносить логику наружу — защита кода. Часть разработчиков просит спрятать логику конфигурации или расширения от конечного пользователя. Городить обфускацию средствами 1С — плохая идея: тот, кому действительно надо, всё равно разберётся, а порядочным клиентам это только создаёт проблемы с обновлениями. Рабочий вариант — вынести чувствительную часть логики в микросервис на Python, скомпилировать его отдельно и разместить в облаке или на своём сервере. 1С в этом случае обращается к сервису по HTTP и не видит исходный код.
Быстрый старт: FastAPI + Pydantic
Ставим сам фреймворк:
pip install fastapi
И ASGI-сервер, на котором FastAPI будет работать — uvicorn:
pip install uvicorn
Создаём файл main.py. Импортируем сам FastAPI и BaseModel из pydantic — он отвечает за автоматическую валидацию входящих данных без единой строчки ручных проверок:
from fastapi import FastAPI, Header, HTTPException, status
from pydantic import BaseModel, Field
Создаём экземпляр приложения:
app = FastAPI(title="1C Integration Service")
Описываем схему данных, которые ждём от 1С. В Pydantic v2 это обычный класс-наследник BaseModel с типизированными полями — если 1С пришлёт не строку в name или вообще забудет это поле, FastAPI сам вернёт 422 с понятным описанием ошибки, до вашего кода дело не дойдёт:
class OnesData(BaseModel):
# min_length защищает от пустых строк, которые 1С иногда шлёт вместо NULL
name: str = Field(..., min_length=1, description="Значение, полученное из 1С")
Для реальной интеграции эндпоинт без авторизации — плохая идея: сервис торчит наружу, и запрос в него может отправить кто угодно. Добавляем простую проверку токена через заголовок запроса. Для продакшена токен читаем из переменной окружения, а не хардкодим в коде:
import os
API_TOKEN = os.environ.get("ONES_API_TOKEN", "dev-only-token")
def verify_token(x_api_key: str = Header(...)) -> None:
# сравнение через == тут допустимо: токен не секрет уровня платёжной подписи,
# для боевого продакшена лучше secrets.compare_digest
if x_api_key != API_TOKEN:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Неверный токен")
Теперь сам эндпоинт. Обратите внимание на model_dump() — в Pydantic v1 использовался .dict(), в актуальной v2 это устаревший алиас, правильный вызов именно model_dump():
from fastapi import Depends
@app.post("/", status_code=201, dependencies=[Depends(verify_token)])
async def process_data(payload: OnesData):
ones_data = payload.model_dump() # Pydantic v2: .dict() устарел, используем model_dump()
print(ones_data["name"])
return {"newdata": ones_data["name"]}
Отдельно стоит добавить health-check — он не завязан на токен и нужен, чтобы Docker/оркестратор мог проверять, что сервис живой, не дёргая бизнес-логику:
@app.get("/health")
async def health_check():
return {"status": "ok"}
Итоговый main.py
Собираем всё вместе — это уже полностью рабочий сервис с валидацией, авторизацией и health-check:
import os
from fastapi import Depends, FastAPI, Header, HTTPException, status
from pydantic import BaseModel, Field
app = FastAPI(title="1C Integration Service")
API_TOKEN = os.environ.get("ONES_API_TOKEN", "dev-only-token")
class OnesData(BaseModel):
name: str = Field(..., min_length=1, description="Значение, полученное из 1С")
def verify_token(x_api_key: str = Header(...)) -> None:
if x_api_key != API_TOKEN:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Неверный токен")
@app.get("/health")
async def health_check():
return {"status": "ok"}
@app.post("/", status_code=201, dependencies=[Depends(verify_token)])
async def process_data(payload: OnesData):
ones_data = payload.model_dump()
print(ones_data["name"])
return {"newdata": ones_data["name"]}
Запуск и проверка
Запускаем сервер в режиме автоперезагрузки и сразу открываем автосгенерированную документацию — в ней можно отправить тестовый запрос прямо из браузера:
uvicorn main:app --reload
Документация доступна по адресу http://127.0.0.1:8000/docs. Когда сервис готов, фиксируем зависимости:
pip freeze > requirements.txt
Упаковываем микросервис в Docker
Dockerfile — используем актуальный slim-образ Python вместо устаревшего buster:
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
docker-compose.yml для локального запуска. Токен передаём через переменную окружения, а не через код:
services:
python:
container_name: ones-micro
build: ./
environment:
ONES_API_TOKEN: ${ONES_API_TOKEN}
restart: always
ports:
- "8000:8000"
.dockerignore — чтобы в образ не попадали виртуальное окружение, кеш и локальные настройки IDE:
__pycache__/
*.pyc
.venv/
venv/
.env
.git
.idea
Dockerfile
docker-compose.yml
Собираем образ и поднимаем контейнер:
docker-compose build
docker-compose up -d
Вызов микросервиса из 1С
На стороне 1С — обычный HTTPСоединение с таймаутом и заголовком авторизации. Ниже пример с комментариями по каждому шагу:
#Область ОбработчикиКомандФормы
&НаКлиенте
Процедура ЗапросКСервису(Команда)
ОтветОтСервера = ЗапросКСервисуНаСервере(ТестоваяСтрока);
КонецПроцедуры
#КонецОбласти
#Область Сериализация
&НаСервереБезКонтекста
Функция СформироватьJSON(Знач Данные)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Данные);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
#КонецОбласти
#Область HTTPЗапрос
&НаСервереБезКонтекста
Функция ЗапросКСервисуНаСервере(Знач name)
Попытка
// 30 секунд — таймаут соединения, без него зависший сервис повесит и 1С
HTTPСоединение = Новый HTTPСоединение("127.0.0.1", 8000,,,, 30);
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/json");
// токен должен совпадать с ONES_API_TOKEN на стороне сервиса,
// иначе FastAPI вернёт 401 ещё до вызова process_data
Заголовки.Вставить("X-Api-Key", "секрет-из-настроек-1с");
HTTPЗапрос = Новый HTTPЗапрос("/", Заголовки);
Данные = Новый Соответствие;
Данные.Вставить("name", name);
ДанныеJSON = СформироватьJSON(Данные);
HTTPЗапрос.УстановитьТелоИзСтроки(ДанныеJSON, КодировкаТекста.UTF8);
HTTPОтветОтСервера = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
// код ответа стоит проверять отдельно: 401 (токен), 422 (валидация)
// и 5xx (сервис упал) требуют разной реакции у вызывающего кода
Если HTTPОтветОтСервера.КодСостояния <> 201 Тогда
ЗаписьЖурналаРегистрации("FastAPI", УровеньЖурналаРегистрации.Предупреждение,,,
"Код ответа: " + HTTPОтветОтСервера.КодСостояния);
КонецЕсли;
Возврат HTTPОтветОтСервера.ПолучитьТелоКакСтроку();
Исключение
ЗаписьЖурналаРегистрации("FastAPI", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
Возврат Неопределено;
КонецФункции
#КонецОбласти 1С как клиент: OAuth2-токен и защищённые вызовы
Статический X-Api-Key из примера выше подходит для внутреннего сервиса в закрытом контуре. Но чаще Python-бэкенд закрыт по OAuth2 (типичный случай для Django OAuth Toolkit, DRF, FastAPI с OAuth2): 1С сначала получает токен по логину и паролю, а потом ходит к защищённым эндпоинтам с заголовком Authorization: Bearer. Ниже — реальный код такого клиента из учебной конфигурации.
Здесь речь про сторону клиента — как 1С получает и носит чужой токен. Обратный сценарий, когда HTTP-сервис 1С сам выдаёт и проверяет токен, разобран отдельно: JWT-авторизация в 1С — выпуск нативным объектом ТокенДоступа и проверка подписи входящего запроса.
Сначала о хранении доступов. В коде ниже Объект.server, Объект.client_id, Объект.client_secret, Объект.username, Объект.password, Объект.access_token, Объект.refresh_token — это не строковые литералы в тексте процедуры, а реквизиты формы, за которыми стоят константы базы. Так и надо: секрет и пароль не хардкодят в модуле — их держат в константах (а ещё лучше — в защищённом хранилище), а пару access_token/refresh_token сохраняют между сеансами, чтобы не запрашивать токен на каждый вызов.
Получение токена — POST на o/token/ с телом grant_type=password. Ответ содержит access_token и refresh_token, оба сразу сохраняем:
&НаКлиенте
Процедура ПолучитьТокен(Команда)
Если Объект.ssl Тогда
SSL = Новый ЗащищенноеСоединениеOpenSSL;
Иначе
SSL = Неопределено;
КонецЕсли;
HTTPСоединение = Новый HTTPСоединение(Объект.server, Объект.port, Объект.client_id, Объект.client_secret,, 30, SSL);
ДанныеЗапроса = СтрШаблон("grant_type=password&username=%1&password=%2", Объект.username, Объект.password);
HTTPЗапрос = Новый HTTPЗапрос("o/token/");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
HTTPЗапрос.УстановитьТелоИзСтроки(ДанныеЗапроса);
Результат = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
Если Результат.КодСостояния = 200 Тогда
Данные = JSONВОбъект(Результат.ПолучитьТелоКакСтроку());
Объект.access_token = Данные["access_token"];
Объект.refresh_token = Данные["refresh_token"];
Иначе
// 400/401 тут — почти всегда неверные client_id/client_secret или логин/пароль
РезультатЗапроса = СтрШаблон("Ошибка. Код состояния: %1 Тело: %2", Результат.КодСостояния, Результат.ПолучитьТелоКакСтроку());
КонецЕсли;
КонецПроцедуры
Путь o/token/ и формат тела здесь — от Django OAuth Toolkit. На другом бэкенде (DRF SimpleJWT, FastAPI с OAuth2) путь и набор полей grant_type будут другими — это первое, что стоит сверить с документацией конкретного сервиса.
Когда access_token истечёт, сервер начнёт отвечать 401. Второй токен нужен ровно для этого: обновление отличается от получения только телом запроса — grant_type=refresh_token вместо пароля, остальная процедура (o/token/, разбор ответа, сохранение новых токенов) идентична:
ДанныеЗапроса = СтрШаблон("grant_type=refresh_token&refresh_token=%1&client_id=%2&client_secret=%3",
Объект.refresh_token, Объект.client_id, Объект.client_secret);
А вот сам защищённый вызов — GET api/v1/products с Bearer-токеном. Обратите внимание: код ответа проверяем отдельно, и 401 здесь означает не «нет доступа навсегда», а «токен протух» — правильная реакция это вызвать обновление токена и повторить запрос:
&НаКлиенте
Процедура ПолучитьТовары(Команда)
Если Объект.ssl Тогда
SSL = Новый ЗащищенноеСоединениеOpenSSL;
Иначе
SSL = Неопределено;
КонецЕсли;
HTTPСоединение = Новый HTTPСоединение(Объект.server, Объект.port, Объект.client_id, Объект.client_secret,, 30, SSL);
HTTPЗапрос = Новый HTTPЗапрос("api/v1/products");
HTTPЗапрос.Заголовки.Вставить("Authorization", СтрШаблон("Bearer %1", Объект.access_token));
Результат = HTTPСоединение.ВызватьHTTPМетод("GET", HTTPЗапрос);
Если Результат.КодСостояния = 200 Тогда
Данные = JSONВОбъект(Результат.ПолучитьТелоКакСтроку());
Иначе
// 401 здесь = токен истёк -> вызвать ОбновитьТокен и повторить запрос
РезультатЗапроса = СтрШаблон("Ошибка. Код состояния: %1 Тело: %2", Результат.КодСостояния, Результат.ПолучитьТелоКакСтроку());
КонецЕсли;
КонецПроцедуры
Полный цикл получается такой: ПолучитьТокен один раз (или когда протух refresh) → рабочие вызовы с Bearer → на 401 вызвать ОбновитьТокен и повторить. Именно так устроена промышленная интеграция 1С с защищённым Python-сервисом, а не «зашили один токен и надеемся».
1С как сервер: HTTP-сервис для Python
Обратное направление интеграции: не 1С дёргает Python, а Python-скрипт обращается к 1С и забирает или отдаёт данные. Для этого в 1С создаётся объект HTTP-сервис с шаблонами URL, а его методы привязываются к HTTP-глаголам. Ниже — обработчики версионированного эндпоинта /v1/products: GET отдаёт список, POST принимает данные и логирует входящий запрос.
#Область API_V1
Функция ProductsV1GET(Запрос)
ДанныеОтвета = СформироватьДанныеОтвета();
Попытка
Возврат ВернутьДанные(ДанныеОтвета, СформироватьСписокТоваров(), 200);
Исключение
Возврат ВернутьОшибку(ДанныеОтвета, ОписаниеОшибки(), 501);
КонецПопытки;
КонецФункции
Функция ProductsV1POST(Запрос)
ДанныеJSON = Запрос.ПолучитьТелоКакСтроку();
Данные = ПрочитатьСтрокуJSON(ДанныеJSON);
// логируем входящий запрос — потом легко отлаживать, что реально прислал Python
ЗаписьЖурналаРегистрации("ProductsV1POST", УровеньЖурналаРегистрации.Информация,,, ДанныеJSON);
ДанныеОтвета = СформироватьДанныеОтвета();
Попытка
Возврат ВернутьДанные(ДанныеОтвета, Новый Структура("message, data", "Товары добавлены в базу", Данные), 200);
Исключение
Возврат ВернутьОшибку(ДанныеОтвета, ОписаниеОшибки(), 501);
КонецПопытки;
КонецФункции
#КонецОбласти
Ключевая деталь для стабильной интеграции — единый конверт ответа {"response": ..., "error": ...}: Python-клиент всегда парсит ответ одинаково, и на успехе, и на ошибке, а не гадает по коду состояния, какое поле читать. Собирается конверт в паре служебных функций:
Функция СформироватьОтвет(Данные, Код)
Ответ = Новый HTTPСервисОтвет(Код);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
ДанныеJSON = ЗаписатьСтрокуJSON(Данные, Истина);
Ответ.УстановитьТелоИзСтроки(ДанныеJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
Функция ВернутьДанные(ДанныеОтвета, Данные, Код)
ДанныеОтвета["response"] = Данные;
Возврат СформироватьОтвет(ДанныеОтвета, Код);
КонецФункции
Функция ВернутьОшибку(ДанныеОтвета, Ошибка, Код)
ДанныеОтвета["error"] = Ошибка;
Возврат СформироватьОтвет(ДанныеОтвета, Код);
КонецФункции
Методы ProductsV1GET/ProductsV1POST привязываются к шаблону URL /v1/products/* в свойствах HTTP-сервиса (корневой URL api), а сам сервис нужно опубликовать на веб-сервере — только после публикации Python сможет достучаться до 1С по HTTP. Логирование тела в журнал регистрации (ЗаписьЖурналаРегистрации) — не украшение: когда обмен «молча не работает», журнал показывает, что именно прислал клиент.
FastAPI vs Node.js/Express: что выбрать для интеграции с 1С
Оба стека одинаково хорошо справляются с ролью «принять HTTP-запрос от 1С и что-то с ним сделать». Разница — в деталях, которые важны именно для интеграций с 1С:
| Критерий | FastAPI (Python) | Node.js / Express |
|---|---|---|
| Валидация входящих данных | Из коробки через Pydantic — схема одновременно и валидирует, и документирует API | Нужна отдельная библиотека (Zod, Joi) и ручная привязка к роутам |
| Документация API | Автогенерация Swagger UI на /docs без дополнительной настройки | Нужен отдельный пакет (swagger-jsdoc и подобные) и описание вручную |
| Экосистема для работы с 1С | Большинство существующих скриптов обмена, парсеров выгрузок 1С и ML/LLM-библиотек — на Python | Сильнее в реалтайме (WebSocket, SSE) и там, где фронтенд и бэкенд на одном языке |
| Порог входа для разработчика 1С | Синтаксис ближе к тому, что 1С-разработчик уже видел в скриптах на Python | Нужно параллельно разбираться с npm-экосистемой и async/await в JS |
Практический вывод: если микросервис — это в первую очередь приём/валидация данных от 1С и дальнейшая передача в Python-экосистему (пандас, LLM, ML-модели), берите FastAPI. Если сервис — часть более крупного Node.js-бэкенда или нужен постоянный канал (WebSocket) с фронтендом, разумнее остаться на Node.js/Express. Подробнее про интеграцию 1С с JavaScript и Node.js — в статье «1С и JavaScript/Node.js».
Частые ошибки при интеграции 1С и Python
- Отсутствие таймаута в
HTTPСоединениена стороне 1С — зависший микросервис повесит и сеанс 1С. - Синхронный блокирующий код внутри
async def— обращение к БД или файлу без await блокирует весь event loop FastAPI, а не только один запрос. - Отсутствие авторизации на эндпоинте, который смотрит в интернет напрямую, без reverse-proxy и firewall-правил.
- Игнорирование кода ответа на стороне 1С: 401 и 422 требуют разной реакции, чем 5xx, но часто в 1С просто проверяют «тело не пустое».
- Хардкод токенов и паролей в коде микросервиса вместо переменных окружения — при коммите в git это моментально становится технической проблемой.
Похожие статьи
- 1С и Kafka: REST-сервис и интеграция через события
- ИИ-агент для 1С на Python и FastAPI: микросервис + LLM
- 1С и JavaScript/Node.js: интеграция через поле HTML и веб-сервисы
- Упаковка FastAPI-приложения в exe через PyInstaller
- Power BI и 1С: выгрузка данных и живой отчёт
Полный курс по Python + 1С
Если хотите не просто повторить один пример, а системно разобраться в интеграции 1С и Python — от HTTP-сервисов 1С до защищённого прокси-сервера на Python и OAuth 2 — приходите на курс «Python + 1С: защищённый OAuth 2 сервер». Разбираем архитектуру интеграции, безопасность и типовые ошибки на реальных кейсах.
Если тема ближе к ИИ-интеграциям — LLM, RAG, MCP-серверы для 1С — на эту задачу отвечает курс «ИИ-агент для 1С: LangChain, RAG и MCP-серверы», где похожий микросервис на FastAPI используется как шлюз между 1С и языковой моделью.
