Статья уже уезжала на сайт Auto-Dzen и в RSS. Потом к тому же конвейеру добавили WordPress — через штатный REST API. Ожидание было простым: отправляешь заголовок с текстом, получаешь готовую запись на сайте. Реальность оказалась ближе к сборке мебели: в коробке лежит не «стол», а набор деталей, и без них стол не стоит.
Два маршрута вместо одного
Интеграция ничего не заменила. Материал, как и раньше, продолжал уходить на сайт Auto-Dzen и в RSS, а WordPress подключился параллельно и получал тот же текст по своему каналу. Отсюда первое практическое правило: у каждого маршрута свои состояния, свои ошибки и свои повторные попытки. Пока канал один, любая осечка выглядит как «сломался конвейер». Когда каналов два, сбой на одном не должен тянуть за собой второй — и наоборот.
Оговорюсь сразу: устройство Auto-Dzen — это наш конкретный случай, а не публично задокументированная схема, и дальше я описываю то, что мы у себя предусмотрели. А вот всё, что касается самого WordPress, лежит в открытой официальной документации, и там всё устроено именно так.
Вход в систему: отдельный пароль
Первый барьер обнаружился на самом входе. Для программного доступа нужен Application Password — это не пароль администратора и не «тот же пароль, только в другом поле». Это отдельный ключ, который создаётся под конкретное приложение и отзывается независимо от основного пароля пользователя. Передаётся он через Basic Authentication, и документация WordPress отдельно предупреждает: только по HTTPS.
Смысл здесь не в том, чтобы усложнить жизнь интеграции. Так конвейеру не приходится хранить живой пароль администратора, а если ключ придётся отозвать, вы отзовёте его один и не тронете всё остальное. Логинов, адресов и ключей я сознательно не привожу: это ровно тот случай, когда детали кейса интересны, а секреты — нет.
Картинка едет отдельно от текста
Следующая неожиданность: главное изображение — не часть статьи. Это отдельный медиаобъект. Файл сначала уходит в медиараздел WordPress, сервер возвращает его идентификатор, и только потом этот идентификатор передаётся записи в поле featured_media. То есть картинка и текст физически едут разными путями и в разное время.
Из этого следует вывод, который экономит много нервов: если запись уже создана, ошибка на шаге загрузки картинки — не ошибка публикации. Повторять в этом случае нужно только загрузку медиа, а не создавать материал заново. Склеить эти две операции в одну «на всякий случай» — самый быстрый способ получить дубли.
Сначала черновик, потом публикация
Запись в WordPress умеет жить в разных статусах: draft, publish, future, pending, private. Значит, «создать материал» и «опубликовать материал» — две разные операции, и лучше их не склеивать. Если между ними что-то падает, у нас остаётся управляемый черновик, который можно проверить глазами, а не наполовину опубликованная статья на живом сайте.
Схема сложилась в два шага: сначала материал создаётся черновиком, затем отдельным запросом переводится в публикацию. Заодно появилось место, куда можно вклиниться руками, если автоматика сделала что-то не то. Автоматизация, у которой нет такой точки остановки, на мой взгляд, уже не автоматизация, а лотерея.
Заголовок, который приехал дважды
Самая бытовая поломка из всех: на сайте заголовок стоял дважды. Похоже, дело было не в API. Заголовок — это отдельное поле записи, но одновременно он часто есть и внутри HTML-контента, который приехал от генератора. В нашем случае тема оформления вывела оба значения — и получился заголовок поверх заголовка.
Вывод из этой мелочи важнее самой мелочи: конвейер должен заранее знать, кто отвечает за какой фрагмент публикации. Если заголовок подставляет шаблон сайта, из тела статьи его надо убирать. Если заголовок приезжает внутри текста, не нужно дублировать его в поле записи. Пока правило не зафиксировано, каждая новая тема оформления будет ломать вёрстку по-своему.
Повторный запуск: где начинается настоящая боль
Самое неприятное место всего кейса. В документации WordPress сказано прямо: POST используется для создания новых ресурсов и не является идемпотентным по смыслу HTTP. Проще говоря, повторная отправка того же запроса может создать вторую запись. Но хуже другое: ошибка на нашей стороне не означает, что записи нет. Тайм-аут или обрыв связи вполне может случиться уже после того, как WordPress всё создал.
Значит, защита не может опираться на ответ сервера. Нужен внешний ключ публикации — исходный идентификатор материала, slug, хэш содержимого или сохранённая связка «материал → ID записи в WordPress». Перед новой попыткой конвейер сначала спрашивает: а не создано ли это уже? И только потом отправляет что-то заново.
Здесь стоит быть честным в формулировках: речь не об абсолютной защите от дублей, а о защите при корректно реализованной логике. Если проверку не написать, никакой REST API сам её не добавит.
Ретраи, которые не тянут за собой весь конвейер
Раз этапов несколько, то и повторные попытки должны быть отдельными. Ошибка при загрузке изображения не должна заново создавать запись. Сбой при переводе черновика в публикацию не должен повторно грузить медиа, у которого уже есть идентификатор. Иначе один сетевой сбой превращается в цепочку дублей, которую потом разгребать руками.
Практическая схема выглядит скучно и именно поэтому работает: у контента, у картинки и у финальной публикации свои состояния и свой retry. Конвейер перестаёт быть одной кнопкой «отправить» и становится несколькими независимыми механизмами, каждый из которых умеет перезапуститься сам, не дёргая соседей.
Что осталось за кадром
Собрать всё это вместе оказалось большим объёмом работы, чем сама интеграция. API у WordPress есть, он документирован и предсказуем — проблема не в нём. Проблема в том, что между «есть API» и «работает без человека» лежит десяток мелких решений: отдельный ключ, отдельная загрузка медиа, черновик перед публикацией, внешний ключ против дублей, независимые ретраи. Автоматизация контента на таком уровне — это в первую очередь работа над ошибками, а не над запросами.
И честная оговорка на будущее. Полностью безлюдной такая схема становится не всегда. Бывают ситуации, когда система не может сама понять, был ли ответ сервера успешным, и тогда ей нужен человек, который посмотрит и скажет: это уже опубликовано или ещё нет. Разница между демонстрацией «API отвечает» и реальной работой без присмотра — как раз в этих ситуациях, а не в красивом ответе сервера на тестовом запросе.
Если интересно не только читать про такие конвейеры, но и собирать их самому — с ИИ-агентами, ретраями и прочей скучной механикой, — заглядывайте в GPT-Лабораторию. Там мы разбираем практическую сторону ИИ: что реально работает, что ломается и как это чинить. Подписаться можно в Telegram (https://t.me/jopotology), во ВКонтакте (https://vk.ru/jopotology) или в MAX (https://max.ru/join/fyssUhvFi8Bo2GK0bm6F_RFKZYTa1LWL8VVJnRMkTHM). Если тема контент-конвейеров вам близка, подписка — самый простой способ не потерять продолжение.
