Приём статей на своём сайте
Как подключить свой сайт к автоматической доставке готовых статей через JSON-RPC.
Что это
Как только очередная статья задачи полностью сгенерирована, Octopus может сам отправить её на ваш сайт и дождаться решения — принята она или нет. Это отдельный от «Callback URL» и обычных JSON-RPC уведомлений механизм: там Octopus просто сообщает о событии, а здесь — спрашивает и получает ответ в том же запросе.
Технически это настоящий JSON-RPC 2.0 запрос с методом
article.deliver
на тот же endpoint, что уже настроен для JSON-RPC интеграции проекта.
Шаг 1. Включите приём в настройках проекта
- Откройте страницу нужного проекта → блок «Интеграция».
- Укажите JSON-RPC endpoint — публичный HTTPS-адрес вашего сайта, куда будут приходить запросы (localhost и адреса из приватных сетей отбрасываются защитой от SSRF — endpoint должен быть доступен из интернета).
- Укажите JSON-RPC secret — им подписывается каждый запрос, см. Шаг 2.
- Отметьте чекбокс «Доставлять готовые статьи на JSON-RPC endpoint».
- Сохраните форму.
Без заполненного endpoint галочка ничего не делает — доставка просто не планируется.
Шаг 2. Примите запрос article.deliver
На ваш endpoint придёт POST с телом:
{
"jsonrpc": "2.0",
"id": 456,
"method": "article.deliver",
"params": {
"article_id": 123,
"task_id": 45,
"project_id": 7,
"title": "Заголовок статьи",
"rubric": "Технологии",
"content": "# Заголовок\n\nТекст статьи в Markdown...",
"images": [
{
"kind": "cover",
"position": null,
"url": "https://cdn.example/cover.jpg",
"alt_text": "Описание картинки"
}
],
"idempotency_key": "octopus-article-123"
}
}
| Поле | Значение |
|---|---|
| rubric | Рубрика статьи, определяется AI. Может быть null — у пайплайнов без стадии-ревьюера. |
| content | Текст статьи в формате Markdown, не HTML. |
| images | Пусто, если у статьи нет картинок. kind — cover или inline, position — только для inline. Скачайте файлы по url при приёме — см. блок ниже. |
| idempotency_key | Стабильный ключ для этой статьи. Если запрос повторяется (см. «Повторные попытки» ниже), значение то же самое — используйте его, чтобы не опубликовать статью дважды. |
Картинки: скачивайте их у себя
images[].url — ссылка на хранилище
провайдера генерации картинок, а не на наш сервер. Мы эти файлы
не храним и не проксируем, срок их жизни определяет провайдер.
Поэтому при приёме статьи скачайте картинки и положите к себе, а в опубликованном тексте ссылайтесь уже на свою копию. Если оставить исходные ссылки, однажды они перестанут открываться — и сразу во всех статьях, которые вы приняли раньше, без единого сигнала с нашей стороны.
Картинки приходят в том же запросе, что и текст: отдельного события для них нет,
а content уже содержит Markdown-разметку со
ссылками на те же url. Заменяйте их на свои в том же шаге, что и скачивание.
Проверка подписи
Если задан JSON-RPC secret, запрос приходит подписанным. Проверяйте подпись — иначе endpoint принимает запросы от кого угодно, кто узнает его адрес.
X-Timestamp— время отправки (unix, секунды).X-Octopus-Signature-V2: sha256=<hmac>— HMAC-SHA256 строки«<X-Timestamp>.<сырое тело>». Проверяйте эту.X-Octopus-Signature: sha256=<hmac>— старый вариант, HMAC только от тела. Оставлен, чтобы не ломать уже работающие интеграции, но защиты от повторной отправки перехваченного запроса у него нет.
$rawBody = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
// Окно ±5 минут: без него подпись вечная и запрос можно проиграть заново.
if ($ts === '' || abs(time() - (int) $ts) > 300) {
http_response_code(400);
exit;
}
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_OCTOPUS_SIGNATURE_V2'] ?? '')) {
http_response_code(401);
exit;
}
Шаг 3. Ответьте решением
Ответ — обычный JSON-RPC 2.0 result с тем же id, HTTP-статус 2xx. Три варианта:
Приняли статью
{
"jsonrpc": "2.0",
"id": 456,
"result": {
"status": "accepted",
"external_id": "site-article-42"
}
}
external_id опционален — id статьи у вас, для сверки.
Отклонили статью
{
"jsonrpc": "2.0",
"id": 456,
"result": {
"status": "rejected",
"retryable": true,
"reason": "Очередь модерации переполнена, повторите позже"
}
}
retryable: true (или поле вовсе не указано) — Octopus повторит попытку позже.
retryable: false — отказ окончательный, повторов не будет
(например, для дублей или нарушения ваших правил контента).
Решение пока не готово
{
"jsonrpc": "2.0",
"id": 456,
"result": {
"status": "pending"
}
}
Для случаев, когда решение принимает модератор и это занимает время. Octopus не будет переспрашивать — статья ждёт вашего отдельного вызова, см. Шаг 4.
Шаг 4 (опционально). Пришлите решение позже
Если на Шаге 3 вы ответили "status": "pending",
когда решение будет готово — позвоните методом article.decision
на обычный API-эндпоинт Octopus, авторизовавшись API-ключом проекта (заголовок
X-API-KEY, тот же, что и для остального REST API):
POST /api/v1/rpc
X-API-KEY: ваш-api-ключ-проекта
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "article.decision",
"params": {
"article_id": 123,
"status": "accepted",
"reason": "Прошла модерацию",
"external_id": "site-article-42"
}
}
status — только accepted или
rejected. Вызвать метод повторно для той же статьи или для статьи,
решение по которой уже вынесено, нельзя — вернётся ошибка, а не тихая перезапись.
Повторные попытки и отладка
- Ждём ответ до 30 секунд; сетевая ошибка, не-2xx статус или
retryable: true— повод для повтора. - График повторов: через 10 минут → 30 минут → 2 часа → 2 часа (и так до 7 попыток), затем статья помечается финально неудачной.
- История всех попыток и решений по каждой статье — на странице проекта «Доставка статей» (
/projects/{id}/deliveries).