#Публикация статей через модули сайта
Статус: Доступно для сайтов на Bitrix (модуль
sapport.connector1.2.1) и WordPress (модульsapport-connector1.1.1). Пакеты для FastAPI и Nuxt в публичный доступ не выпущены. Заказ публикации сегодня выполняется в кабинете Sapport; публичного серверного API публикации нет (см. API статей, статус Планируется).
Модуль сайта — это небольшой плагин, который вы привязываете к вашему сайту. Он раз в несколько минут спрашивает у платформы, есть ли для него задания, проверяет подпись задания, применяет его к вашей CMS и сообщает итог. Страница объясняет, как устроено задание, чем платформа ограждает ваш сайт и что делать при конфликтах. Она предназначена и для владельца сайта, и для разработчика адаптера.
Доступ: в кабинете — право seo.manage для публикации, seo.view для просмотра. Платно: нет. ПДн: нет.
#Как это работает
- Вы привязываете сайт: в кабинете создаётся одноразовый код привязки, вы вводите его в модуле. Платформа выдаёт сайту секрет (показывается один раз).
- В кабинете вы выбираете статью и нажимаете «Опубликовать на сайт через модуль», указав режим: черновик или публикация.
- Платформа создаёт задание, подписывает его и ставит в очередь для вашего сайта.
- Модуль забирает задания (опрос раз в 10 минут; ежечасный heartbeat сообщает число ожидающих заданий), проверяет подпись и применяет.
- Модуль возвращает итог:
appliedс адресом страницы либоskipped,conflict,failedс кодом причины. В кабинете у статьи и у сайта виден статус, адрес страницы, идентификатор записи в вашей CMS и ошибка.
Адрес платформы (api_base) для модуля — домен ячейки привязки: RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id. Ячейку определяет префикс кода привязки.
Полный протокол «облако — модуль» (привязка, heartbeat, опрос заданий, итог, отвязка, подпись запросов HMAC) и коды ошибок описаны в
PROTOCOL.mdиERRORS.mdпакета коннектора. Здесь — то, что нужно, чтобы понимать публикацию статей.
#Задание
| Поле | Тип | Описание |
|---|---|---|
job_id | string (uuid) | Идентификатор задания |
kind | "article" | "post" | Источник: статья или пост |
action | "upsert" | "unpublish" | Создать или обновить / снять с сайта |
source_id | string (uuid) | Идентификатор статьи на платформе |
version | integer | Версия публикации этого источника на этом сайте |
payload | object | Содержимое (ниже) |
payload_sha256 | string | Хеш канонического JSON полезной нагрузки |
kid | string | Идентификатор ключа подписи |
sig | string | Подпись Ed25519 в base64 |
issued_at | integer | Время выдачи, секунды Unix |
expect_cms_id | string, необязательно | Идентификатор записи в CMS, которую платформа ожидает обновить; есть, только если платформа уже применяла этот источник на этом сайте |
#Содержимое (payload)
| Поле | Тип | Описание |
|---|---|---|
title | string | Заголовок |
slug | string | Адрес страницы (ЧПУ); применяется только при создании |
html | string | Готовый очищенный HTML статьи |
excerpt | string | Анонс. Для статьи — её мета-описание (у статей площадки сначала анонс из блога); для поста — пустая строка |
cover_url | string | null | Обложка: абсолютный https-адрес с домена ячейки |
cover_alt | string | null | Подпись обложки; сегодня равна заголовку |
seo | object | title, description, canonical (сегодня всегда null) |
tags | array of string | До 10 тегов, каждый до 50 знаков |
category_hint | string | null | Подсказка раздела |
locale | string | Язык статьи |
publish_mode | "draft" | "publish" | Желаемый режим |
scheduled_at | integer | null | Отложенная публикация; сегодня всегда null |
recreate | true, необязательно | Пересоздать запись, удалённую на сайте |
{
"job_id": "00000000-0000-4000-8000-000000000001",
"kind": "article",
"action": "upsert",
"source_id": "11111111-1111-4111-8111-111111111111",
"version": 1,
"payload": {
"title": "Пептиды: полный гид",
"slug": "peptidy-polnyj-gid",
"html": "<p>Текст статьи</p>",
"excerpt": "",
"cover_url": "https://formula-cream.pro/storage/v1/object/public/sapport-files/covers/a.webp",
"cover_alt": "Пептиды: полный гид",
"seo": { "title": "Пептиды", "description": "Описание", "canonical": null },
"tags": ["пептиды", "уход"],
"category_hint": "ingredients",
"locale": "ru",
"publish_mode": "draft",
"scheduled_at": null
},
"payload_sha256": "bbaee6d585670f93302af42addc4d8c2cb70638d1571e4616576a492fe0d26d1",
"kid": "test-1",
"sig": "mHgK/Ud/fbDzwnjpKiSKtyi49Mzth+sOOWc+ti2oXYHzzu0CtX92SxKVVH3aNYfuiatMnakLIsODBhieLRHfBw==",
"issued_at": 1790000000
}
Пример взят из тестовых векторов пакета (ключ test-1 — тестовый и боевыми ячейками не используется).
#Пределы
| Что | Предел |
|---|---|
| HTML статьи | не более 2 000 000 байт UTF-8 |
Весь payload в JSON | не более 3 000 000 байт |
| Обложка | JPEG, PNG или WebP, до 5 МБ |
| Тегов | до 10, до 50 знаков каждый |
| Возраст подписи | 7 суток; старше — skipped expired |
Статья, превысившая предел, не превращается в задание: в кабинете вы увидите исход «слишком велика» либо «пустая».
Относительные адреса картинок и ссылок в HTML превращаются в абсолютные на домен ячейки. Модуль скачивает медиа только с адреса платформы (api_base), без переадресаций: обложка с чужого CDN завершит задание ошибкой media_foreign_origin.
#Подпись задания
Каждое задание подписано Ed25519 закрытым ключом ячейки. Это защищает ваш сайт от подмены задания по пути и от чужих заданий:
- подписывается канонический JSON конверта
{action, issued_at, job_id, kind, payload_sha256, site_id, source_id, version}(иexpect_cms_id, если он есть); site_idв конверте — идентификатор вашего сайта из привязки, поэтому задание другого сайта подпись не пройдёт;kidпоказывает, каким ключом подписано; префикс идентификатора указывает ячейку:ru-,en-,id-;- открытые ключи ячеек публикуются в файле
integrations/common/job-keys.jsonпакета коннектора, по одному наkid. Если модуль не знаетkid(ключ ротировали), он сообщает «нужно обновить модуль» и не гасит задание: платформа выдаст его снова.
Канонический JSON: ключи объектов сортируются по кодовым единицам UTF-16 рекурсивно, без пробелов, числа только целые в пределах ±(2^53−1), вложенность не глубже 32; отклоняются дробные числа, одиночные суррогаты и повторяющиеся ключи. Тестовые векторы и реализация — в пакете коннектора (каталог tests/_vectors/).
Подпись самих запросов модуля к платформе (HMAC-SHA256 с заголовками X-Sapport-Site, X-Sapport-Ts, X-Sapport-Nonce, X-Sapport-Sig, окно ±300 секунд) — отдельный механизм привязки сайта; он описан в протоколе коннектора.
#Что делает модуль с содержимым
| Поле задания | Bitrix | WordPress |
|---|---|---|
| Заголовок | NAME | post_title |
| Текст | DETAIL_TEXT (список разрешённых тегов) | post_content (wp_kses_post) |
| Анонс | PREVIEW_TEXT | post_excerpt |
Адрес (slug) | CODE, только при создании | post_name, только при создании |
| Теги | TAGS | Метки |
| Раздел | Из разрешённых разделов по подсказке | Рубрика |
| SEO | SEO-шаблоны элемента | Yoast или Rank Math |
| Черновик, публикация | ACTIVE | draft, publish, future |
| Обложка | Картинки элемента | Миниатюра записи |
| Снятие с сайта | ACTIVE = N | Статус draft |
Модуль в любом случае чистит HTML сам — независимо от очистки на платформе.
#Потолок режима
Владелец сайта может ограничить модуль: «только черновики». Тогда задание на публикацию применится как черновик, а статья, которую человек уже опубликовал на сайте, вернёт конфликт published_awaits_approval.
#Версии
- Каждое изменение источника (правка статьи и повторная отправка) создаёт новую версию задания:
max + 1. - Повторная отправка неизменённой статьи новой версии не создаёт: кабинет отвечает «уже в работе».
- Устаревшие невыданные версии помечаются
superseded. - Версия, не новее уже применённой, получает
skipped stale_version— это нормальный результат при повторах. - Снятие с сайта (
unpublish) не создаёт новую версию и допустимо только если на сайте уже есть применённая публикация; иначе — отказ «не опубликовано». - «Создать заново» — отправка с
recreate: trueи безexpect_cms_id, когда запись удалили на сайте и вы хотите опубликовать её снова.
#Итог задания
Модуль возвращает один итог на задание; первый записанный итог окончателен и не перезаписывается.
| Итог | Поля | Смысл |
|---|---|---|
applied | cms_id, url | Применено; url есть у опубликованной записи |
skipped | error | Пропущено (устаревшая версия, срок подписи, нечего снимать) |
conflict | error | Конфликт со страницей на сайте |
failed | error | Ошибка |
Требования к полям: cms_id — ^[A-Za-z0-9_-]{1,64}$; url — только http или https; error — до 500 знаков. Временные сбои (база, сеть, потеря аренды) модуль не отправляет: задание вернётся через 15 минут.
#Коды конфликтов и пропусков
| Код | Итог | Смысл | Что делать |
|---|---|---|---|
stale_version | skipped | Версия не новее применённой | Ничего: норма при повторах |
expired | skipped | Подписи больше 7 суток | Создать задание заново |
deleted_on_site | skipped (при снятии) / conflict (при публикации) | Запись удалили на сайте | Для публикации — «Создать заново» |
not_found | skipped | Снимается источник, который здесь не публиковался | Ничего |
edited_on_site | conflict | Статью правили на сайте после модуля | Обновить вручную или откатить правку и отправить снова |
published_awaits_approval | conflict | Потолок «черновик», а запись уже опубликована человеком | Поднять потолок или перенести вручную |
cms_id_mismatch | conflict | Платформа ждёт другую запись | Обратиться в поддержку Sapport |
issued_in_future | failed | Часы сайта отстают более чем на 5 минут | Исправить время сервера |
Полный перечень (подпись, обложка, база сайта, настройка разделов) — в ERRORS.md пакета коннектора.
#Что вы видите в кабинете
У статьи и у сайта (блок «Публикации»): заголовок, режим публикации, статус задания (в очереди, выдано, применено, пропущено, конфликт, ошибка), адрес страницы, идентификатор записи в CMS и текст ошибки. Одновременно список заданий читают сотрудники с правом seo.view, seo.manage или admin.api_keys.
#Публичного API для этого пока нет
Кабинетные маршруты публикации требуют сессии сотрудника и для внешних систем не предназначены. Серверный вызов «опубликовать» с ключом — часть будущего API статей: POST /articles/{id}/publish с target: connector.
#Открытые вопросы
scheduled_at(техническое). Сегодня всегдаnull; модуль с отложенной публикацией (futureв WordPress) получит её только когда платформа начнёт её передавать.canonical(техническое). Всегдаnull: управление каноническим адресом на стороне платформы пока не реализовано.- Пакеты FastAPI и Nuxt (к владельцу). В публичный доступ не выложены; станут ли публичными, не решено.
- Домен-псевдоним (техническое). Сайт на домене, который не привязан и не является
www-вариантом привязанного, на подписанных запросах получает400; поведение не закреплено в документации привязки. - Публичный адрес
job-keys.json(техническое). Файл лежит в репозитории пакета; публичный адрес для его скачивания не определён.