verified by n8n: Как мы сгенерировали ноду из OpenAPI и почему пришлось выбросить рукописную версию

от автора

Привет, Хабр! Меня зовут Андрей и я развиваю портал для разработчиков в корпоративном мессенджере Пачка. Эта статья про то, как мы превратили рукописную ноду для n8n в артефакт, который генерируется из одного файла спецификации, и как она прошла официальную верификацию и встала в каталог n8n со статусом verified by n8n.

По дороге мы выбросили целиком первую версию ноды и перевели всю документацию API на новый процесс генерации. Ниже — как это устроено внутри, с кодом и граблями.

Если коротко, что получилось: правка в одном .tsp-файле автоматически расходится в документацию, CLI, SDK и n8n-ноду, а CI публикует всё по реестрам. Нода больше не может отстать от API, потому что собирается из того же источника.

Точка отсчёта: документация без единого источника

Публичная документация API Пачки существует с 2020 года. Технически это был отдельный самописный сайт на рукописных HTML и JS. Каждый эндпоинт описывался HTML-таблицей вручную. Единого машиночитаемого источника, например OpenAPI, не было.

Со временем справочника перестало хватать. API рос, у разработчиков появлялись запросы на SDK под разные языки, кто-то собирал свои сторонние библиотеки под Пачку сам. Но из рукописных HTML-таблиц ничего не сгенерируешь: как источник для кодогенерации они бесполезны. Нам нужен был один машиночитаемый контракт, из которого можно собирать и документацию, и SDK. Источником правды должна стать спецификация, а всё остальное — производным от неё.

Зачем нам вообще n8n

n8n — open-source платформа для no-code-автоматизаций: визуальный редактор, сотни готовых нод, события и действия соединяются мышкой. Мы заметили, что ей уже пользуются наши клиенты, многие разворачивают её на собственном сервере. Для продукта с открытым API это очевидный канал: дать пользователям силу API без единой строчки кода, чтобы собрать бота, согласование по кнопкам, выгрузку задач во внешние сервисы. Отдельно подкупает, что готовый сценарий в n8n — это просто JSON: им можно поделиться и импортировать в пару кликов, а собрать его теперь способен и ИИ — он знает ноды и выдаёт рабочий workflow. И всё крутится внутри n8n, без отдельного хостинга под каждую интеграцию.

Чтобы Пачка нормально жила внутри n8n, ей нужна своя нода. И мы сделали её. Руками.

Первая версия: рукописная нода и её потолок

Осенью 2025-го собрали рукописную ноду на TypeScript, выложили на npm и запустили закрытую бету на пятерых добровольцах. Базовые методы (сообщения, пользователи, чаты) поехали быстро, сложные (формы, разворачивание ссылок, загрузка файлов через S3) — со скрипом. В цифрах первая версия выглядела так:

  • ~5300 строк рукописного кода, 33 коммита в отдельном репозитории;

  • 12 ресурсов, частичный CRUD;

  • distribution только через npm — пользователи ставили её вручную в self-hosted n8n.

В ноябре 2025-го отправили ноду на верификацию в n8n, чтобы она прошла все проверки и стала доступна в облаке, а не только в коробке. И тут начался цикл, знакомый всем, кто заводил пакет во внешний каталог. Каждую отправленную версию сначала проверяет алгоритм, а дальше она уходит на ручное ревью командой n8n.

Первая проблема, что каждое такое ревью — примерно 3-4 недели ожидания. Любая, даже мелкая правка означает новый круг. Вторая проблема вытекает из первой — нужно основательно готовить ноду к отправке, учитывать все официальные гайдлайны n8n и даже опыт коммьюнити, что вручную делать оказалось непросто. Мы отправляли ноду раз пять, и каждый раз её возвращали на очередной мелочи, при этом API за это время развивался: получалось, что новый эндпоинт в ноду надо дописать руками, замечание ревьюера — поправить и пересобрать. Рукописная нода к живому API превратилась в догонялку.

Вывод, который мы для себя зафиксировали: пока рукописная v1 пройдёт ревью, она уже устареет относительно API. Так и вышло — первая версия верификацию так и не прошла. Мы остановили правки и поняли, что менять надо сам процесс.

Разворот: один источник на TypeSpec

Здесь история перестаёт быть про ноду и становится про процесс. И начинается она с документации публичного API.

На 2025 год и начало 2026-го пришёлся рост агентских инструментов разработки: Claude Code, Cursor, Codex. На этой волне сошлись два желания: проверить на большой реальной задаче, на что способна агентская разработка, и наконец собрать тот самый машиночитаемый источник, к которому мы подступались выше.

Под это мы выбрали TypeSpec — типизированный язык, который компилируется в openapi.yaml. Из коробки он даёт всё, что нужно для единого источника: операции, модели, переиспользуемые типы, enum’ы, union’ы под разные коды ответа, описания и примеры через @doc и @example.

Но «голый» OpenAPI ничего не знает про устройство именно нашего API: какие у операции нужны права, на каком тарифе она доступна, как устроена пагинация. Это доменное знание мы зашили в спеку своими расширениями (x-*) и научили каждый генератор их читать:

  • **x-requirements** (68 операций) — нужный OAuth-скоуп и тариф (scope: "messages:create", plan: "corporation"). В ноде превращается в notice: «requires admin permissions», «Corporation plan».

  • **x-scope-roles** — матрица «скоуп → роли» (owner / admin / user / bot). В ноде даёт подсказку, кто вообще может вызвать операцию.

  • enum’ы (25 штук: типы файлов, сущностей, сортировок, событий вебхуков) плюс x-enum-descriptions — в ноде становятся выпадающими списками с понятными подписями, а не строковым полем, куда надо угадать значение.

  • **x-paginated** (15 эндпоинтов) — курсорная пагинация, в ноде из неё вырастает переключатель Return All / Limit с автодокачкой страниц.

  • **@example** на каждом поле — дефолтные значения и примеры в полях ноды.

И главное — пишется это один раз, а читают расширения все генераторы. Те же x-requirements и x-scope-roles в документации превращаются в бейджи требований на странице метода и интерактивный справочник «скоуп → роль». @example растекается в примеры запросов в доке и в CLI. x-paginated даёт флаг --all в CLI и пагинаторы в SDK. То есть TypeSpec дал позвоночник, а «мясо» — это наши расширения поверх него, и одно и то же знание из спеки расходится во все артефакты сразу.

Схематично весь пайплайн выглядит так:

typespec.tsp    │ tsp compile    ▼openapi.yaml ──────────────┬──────────────────┬─────────────────────┐    │ overlay:apply        ▼                  ▼                     ▼    ▼                 packages/cli       sdk/* (6 языков)     integrations/n8nopenapi.en.yaml           │ generate-cli      │ CI: generate        │ generate-n8n    │                     ▼                   ▼                     ▼    ▼                 npm (@pachca/cli)   npm, PyPI, JitPack,   npm (n8n-нода)apps/docs                                 SPM, Go modules    │ generate-llms, next build    ▼  портал + llms.txt + per-endpoint/per-guide .md

Зависимости между шагами описаны декларативно в turbo.json, так что это не набор скриптов, которые надо запускать по очереди. Меняешь .tsp, для новых строк добавляешь английский перевод в overlay (про него ниже), запускаешь turbo build — он одной командой пересобирает доку, CLI и ноду в правильном порядке, а CI раскатывает результат: пакеты в реестры, документацию на портал. SDK на шесть языков генерируются из той же спеки, но собираются отдельным шагом: каждому языку нужен свой тулчейн (go build, gradle, dotnet, swift), поэтому они вынесены в свою CI-джобу. Вся эта пересборка — чистая детерминированная генерация, агент тут не нужен.

Где агенты действительно нужны — это новый код и аудит. Сам проект (генераторы, тесты, миграции) мы писали с активной помощью агентов. Spec-driven проект с десятком артефактов раньше потребовал бы отдельной команды, а так его реально вести малыми силами. Кстати, основной объём я по сути веду один, но интерес к порталу в команде общий: генерацию и публикацию SDK сразу на шесть языков (от TypeScript до Swift) завёл другой наш инженер.

Заодно поменялся и сам сайт. Из справочника по методам он вырос в полноценный портал для разработчиков: гайды, примеры на разных языках, CLI, SDK, готовые сценарии и нода.

Как нода генерируется из спеки

Нода живёт в монорепозитории как ещё один артефакт (integrations/n8n/): генератор generate-n8n.ts читает уже разрешённый OpenAPI и строит описания ресурсов, главный класс ноды и credentials. Сложные вещи — кнопки, формы, загрузка файлов, поиск — генерируются в программном стиле execute(): в декларативном их нормально не реализовать.

Параллельно нода стала просто удобнее для no-code-пользователя, и это тоже выпало из спеки:

  • автопагинацияReturn All / Limit вместо ручных per/page;

  • Simplify — переключатель, оставляющий в ответе только ключевые поля (меньше шума при drag-and-drop);

  • searchable dropdowns — поиск по чатам и сотрудникам прямо в выпадающих списках вместо ввода ID;

  • полный CRUD по задачам вместо одного создания;

  • загрузка файлов через S3 (по URL или из бинарных данных предыдущего узла).

Покрыли всё тестами: 42 mock-теста на 52 операции и 33 интеграционных против реального API, плюс юнит-тесты — суммарно под 400.

Вторая нода: триггер и особенность вебхуков Пачки

Рукописная v1 умела только действовать: отправить сообщение, создать задачу, загрузить файл. Реагировать на события в самой Пачке она не могла. В новой версии появилась вторая нода — Pachca Trigger, которая слушает вебхуки и запускает workflow на событие (новое сообщение, нажатие кнопки, реакция и т.д., всего 16 типов).

Здесь нас ждала особенность самой Пачки: у бота только один слот под webhook-URL, и новая запись затирает предыдущую. Поэтому у триггера два режима регистрации:

  • автоматический — нода сама прописывает URL боту через API при активации workflow;

  • ручной — вы вставляете URL в настройки бота сами.

Ручной режим выручает, когда боту нельзя давать права на запись или когда одним ботом пользуются и тест, и продакшен (иначе тестовый запуск перезатрёт продовый URL). Это, кстати, был источник одного из багов ранних версий — автоматический режим пытался прописать URL бот-токеном без нужного скоупа и получал 403. В итоге мы развели режимы явно.

Обратная совместимость: VersionedNodeType и замороженный v1

Самым нервным куском оказалась обратная совместимость. На v1 уже работали реальные автоматизации у клиентов, и переименование ресурсов и операций (в v2 мы причесали имена: reactionsreaction, sendcreate и т.д.) не должно было сломать ни одного существующего workflow.

Решение нашлось в механизме самого n8n — VersionedNodeType. Одна нода, внутри несколько версий, по умолчанию отдаётся новая:

export class Pachca extends VersionedNodeType {  constructor() {    const baseDescription = {      name: 'pachca',      defaultVersion: 2,      // ...    };    const nodeVersions = {      1: new PachcaV1(baseDescription),      2: new PachcaV2(baseDescription),    };    super(nodeVersions, baseDescription);  }}

Старый класс мы заморозили буквально как есть — скопировали из последней опубликованной версии 1.0.27 и закрыли от правок комментарием:

// ============================================================================// PachcaV1.node.ts — FROZEN V1 node class (from n8n-nodes-pachca@1.0.27)// DO NOT EDIT — this file is frozen for backward compatibility// ============================================================================

Дальше работает так:

  • существующая нода в чужом workflow сохраняет typeVersion: 1 и использует V1-класс — все параметры и поведение прежние;

  • новые ноды по умолчанию создаются на V2;

  • общий SharedRouter на лету транслирует v1-имена ресурсов и операций в v2, так что под капотом обе версии ходят в один и тот же код;

  • в Node Creator показываются только v2-операции, без дубликатов;

  • на v1-нодах n8n сам рисует жёлтый баннер «доступна новая версия» — это просто уведомление, переделывать ничего не нужно.

В генераторе под это пришлось завести несколько слоёв совместимости (таблицы соответствий v1↔v2 для ресурсов, операций, параметров, коллекций), чтобы старые значения сохранялись байт в байт. Зато обновление для пользователя получилось безболезненным: пакет называется так же, новая версия встаёт поверх старой, переезд на v2 — опциональный и постепенный.

Английский интерфейс через OpenAPI Overlay

Verified-нода обязана быть полностью на английском: интерфейс и документация. При этом основная спека у нас русская (это контракт с бэкендом и наша основная аудитория). Переводить руками по второму кругу не вариант, поэтому мы используем стандартный механизм самой спецификации, OpenAPI Overlay. Это отдельный файл, который адресует строки к нужным местам спеки по JSONPath и подменяет описания:

overlay: 1.0.0info:  title: Pachca API — English translations  version: 1.0.0actions:  - target: $.paths['/profile'].get    update:      description: |-        Get profile        Retrieve information about your own profile.

При сборке overlay:apply накладывает слой на русскую спеку и получает openapi.en.yaml, из которой и генерируется нода. Русская спека питает документацию и API-референс, английская — ноду. Отдельный скрипт-валидатор следит, что overlay покрывает все поля, так что новое поле в API нельзя забыть перевести: сборка просто упадёт.

Что требует n8n для верификации

Раз уж прошли этот путь — вот сжатый чек-лист требований к verified-ноде (гайдлайны n8n):

Требование

Деталь

Один сервис на пакет

Одна интеграция + опционально триггер. Не дубль, не Logic/Flow-ноды

TypeScript

Проверка пакета инструментом n8n-node, скан npx @n8n/scan-community-package

Никаких рантайм-зависимостей

Запрещены внешние зависимости, доступ к env и файловой системе — данные только через параметры

English only

Интерфейс и вся документация: параметры, описания, ошибки, README

Источник пакета

Открытый репозиторий с лицензией MIT, npm привязан к GitHub (URL и автор совпадают), имя n8n-nodes-*

Иконки и codex

Светлая/тёмная иконки, codex-файл с категориями

Provenance

Публикация из GitHub Actions с provenance-statement (с весны 2026 — обязательно)

Конкретно нам ревьюеры прислали список обязательных правок: убрать эмодзи из названий опций, убрать недокументированное поле в codex, почистить мёртвый код, убрать шаблоны форм. Все правки легли в один цикл: чиним в спеке или генераторе, проверяем разом в SDK, CLI и ноде.

Как держим всё в синхроне: аудит API

Чтобы нода (и остальные артефакты) не отставала от бэкенда, у нас описан процесс внутреннего аудита API, он лежит в открытом репозитории, в папке docs/ рядом с инженерными регламентами. По этому регламенту агент регулярно сверяет дефолтную ветку бэкенда со спекой: новые эндпоинты, параметры, поля ответов, payload вебхуков, коды ошибок, версии и changelog’и пакетов. Любое расхождение вносится в .tsp, прогоняется генерацией и расходится во все артефакты сразу. Та самая ситуация «нода отстала от API», из-за которой умерла v1, теперь технически невозможна: нода обновляется в том же цикле, что и сам API.

Цикл замыкается на стороне n8n. После верификации не нужно проходить её заново на каждый релиз: n8n сам подхватывает новые npm-версии ноды, прогоняет быстрое ревью и включает в ближайший релиз каталога. Получается сквозной конвейер: правка в спеке → регенерация → публикация в npm → обновление ноды в облаке.

Результат

В начале мая 2026-го Пачка появилась в официальном каталоге n8n со статусом verified by n8n, двумя карточками — Pachca и Pachca Trigger. Попасть туда непросто — российских сервисов в каталоге единицы. Контраст со временем нагляден. Рукописную v1 ревью гоняло по кругам несколько месяцев, и в каталог она так и не попала. v2 мы начали в марте, 8 апреля получили правки от ревью, внесли их в тот же день — и нас сразу одобрили. 1 мая ноду выложили в официальный каталог, ещё и поблагодарили за то, что так основательно её пересобрали.

Изображение

В витрину встала не только нода, но и документация: со страницы ноды в каталоге ссылка ведёт прямо в наш раздел гайдов: установка, ресурсы, триггер, миграция, тестирование, разбор ошибок и готовые workflow для импорта. Английский остаётся у самой ноды, развёрнутые гайды и API-референс — русскими, для наших разработчиков.

Для пользователя обновление безболезненное: в облаке нода ставится из каталога в один клик, в self-hosted — через Community Nodes → Update. Существующие workflow остаются на своей версии, новые создаются на v2.

Что дальше: агент поверх ноды

Verified-нода и триггер — это фундамент, а не финал. Поверх них мы уже собираем AI-агента, который живёт прямо в Пачке: его зовут из треда или лички, он ищет нужные чаты, людей и сообщения, подтягивает контекст обсуждения и отвечает по делу. Технически это обычный workflow: Pachca Trigger ловит обращение, дальше языковая модель с набором инструментов, а инструменты — это наши же ноды Pachca.

Главный вывод по агентам оказался неочевидным. Просто пометить операции ноды как usable-as-tool и отдать их агенту — мало: на широком наборе инструментов он путается, какой вызвать. Гораздо стабильнее заворачивать конкретный навык в отдельный sub-workflow-инструмент со строгим контрактом. Например, «найти сотрудника»: снаружи у инструмента один вход, а внутри sub-workflow сам решает, искать по имени или по user_id. Агент не думает лишний раз и реже ошибается. Для n8n это нормальная практика — от него ждут строгости и предсказуемости, а не магии.

Такие навыки-сабворкфлоу собираются из тех же нод (Get a message, Create a thread, Get many messages) и тоже шарятся как JSON — заготовку можно отдать желающим и смотреть, как они дорабатывают её под свои кейсы.

А дальше — самое интересное для нас: научиться генерировать и эти агентские сценарии из той же спецификации. Тогда и сам агент, и его навыки станут не отдельным кодом, который поддерживаешь руками, а ещё одним артефактом из одного источника. Тот же принцип, что вытащил нас из догонялки с нодой, мы хотим распространить на уровень выше.


Что мы из этого вынесли

  • Рукописная интеграция к живому API — это технический долг, который растёт быстрее, чем его гасишь. Внешнее ревью просто обнажает то, что и так копилось.

  • Один источник правды окупается, когда артефактов много. Когда из спеки растут доки, CLI, шесть SDK и нода, одна правка чинит всё разом — а раньше это была бы куча отдельных задач и столько же способов разойтись.

  • Требования витрины — это бесплатный внешний код-ревью. Запрет рантайм-зависимостей и мёртвого кода, английский UI, provenance-публикация: после такого прохода качество объективно выше.

  • Актуальность — это процесс, а не разовое действие. Регулярный аудит плюс генерация держат библиотеки синхронными.

  • Рутину делает генерация, а не агенты. Пересборка детерминирована и в агенте не нуждается. Агенты подключаются там, где нужна голова — новый код и анализ изменений бэкенда. Именно эта связка позволила небольшой команде вести проект уровня, который раньше потребовал бы отдела.

Если пользуетесь n8n — нода Пачки уже в каталоге. А посмотреть, как из одного .tsp-файла вырастают доки, CLI, SDK и нода, можно в открытом репозитории: github.com/pachca/openapi.

ссылка на оригинал статьи https://habr.com/ru/articles/1044888/