← Все инструкции

Приём статей на своём сайте

Как подключить свой сайт к автоматической доставке готовых статей через JSON-RPC.

Что это

Как только очередная статья задачи полностью сгенерирована, Octopus может сам отправить её на ваш сайт и дождаться решения — принята она или нет. Это отдельный от «Callback URL» и обычных JSON-RPC уведомлений механизм: там Octopus просто сообщает о событии, а здесь — спрашивает и получает ответ в том же запросе.

Технически это настоящий JSON-RPC 2.0 запрос с методом article.deliver на тот же endpoint, что уже настроен для JSON-RPC интеграции проекта.

Шаг 1. Включите приём в настройках проекта

  1. Откройте страницу нужного проекта → блок «Интеграция».
  2. Укажите JSON-RPC endpoint — публичный HTTPS-адрес вашего сайта, куда будут приходить запросы (localhost и адреса из приватных сетей отбрасываются защитой от SSRF — endpoint должен быть доступен из интернета).
  3. Укажите JSON-RPC secret — им подписывается каждый запрос, см. Шаг 2.
  4. Отметьте чекбокс «Доставлять готовые статьи на JSON-RPC endpoint».
  5. Сохраните форму.

Без заполненного 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).