Перейти к содержанию

Справочник API: SEO-генератор

Версия: v7.1 | Сервер: pyp.ru (88.99.12.74) | Дата: 2026-03-24


Подключение

Базовый URL: http://localhost:8001 (на сервере)

Аутентификация: Заголовок X-API-Key во всех запросах (кроме /health).

X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a

Формат ошибок: Все ошибки возвращаются в формате JSON:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Описание ошибки",
    "details": []
  }
}

Эндпоинты

GET /health

Проверка работоспособности сервиса. Аутентификация не требуется.

curl http://localhost:8001/health

Ответ (200):

{
  "status": "healthy",
  "service": "seo-python-service",
  "version": "1.0.0",
  "environment": "production"
}

POST /api/v1/category-generate

Генерация SEO-текста для категории. Полный конвейер: парсинг страницы, генерация лемм, генерация SEO-фраз, Генератор, Редактор, проверки качества.

Запрос:

curl -X POST http://localhost:8001/api/v1/category-generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a" \
  -d '{
    "task_index": "borsh-001",
    "category_url": "https://povar.ru/list/borsh/",
    "category_name": "Борщ"
  }'

Параметры запроса (JSON body):

Поле Тип Обязательное По умолчанию Описание
task_index строка Да Уникальный идентификатор задачи
category_url строка Да URL категории на povar.ru
category_type строка Нет list Тип: list или menu
category_name строка Нет Парсится из страницы Название категории
recipe_titles массив строк Нет Парсятся из страницы Список названий рецептов
lemmas_txt строка Нет Генерируются автоматически Леммы для основного текста
target_chars_no_spaces число Нет 2000 Целевой объем (1800-2200)
target_chars_top_no_spaces число Нет 420 Целевой объем zone_top (150-600)

Query-параметры:

Параметр Тип По умолчанию Описание
force boolean false Если true — удаляет ключ идемпотентности перед обработкой, позволяя перегенерацию

Пример с force: POST /api/v1/category-generate?force=true

Ответ (200) — успех:

{
  "success": true,
  "output_type": "success",
  "content": {
    "zone_h1": "Борщ - рецепты на любой вкус",
    "zone_top": "Лучшие рецепты борща с фото пошагово...",
    "zone_txt": "Основной SEO-текст категории...",
    "zone_t": "Борщ: 49 рецептов с фото - Повар.ру",
    "meta_description": "Рецепты борща на любой случай...",
    "chars_no_spaces": 1920,
    "chars_no_spaces_top": 380,
    "recipe_count": 49
  },
  "parsed_category": {
    "category_name": "Борщ",
    "category_url": "https://povar.ru/list/borsh/",
    "recipe_titles": ["Украинский борщ", "Борщ с фасолью"],
    "recipe_count": 49
  },
  "checker_report": {
    "overall_pass": true
  },
  "quality_check": {
    "status": "passed",
    "uniqueness_pct": 100.0,
    "text_uid": "69c194eaab4f8",
    "water_percent": 17.0,
    "spam_percent": 0.0
  },
  "iteration_count": 2,
  "warnings": []
}

Ответ (200) — ошибка генерации:

{
  "success": false,
  "output_type": "dlq",
  "error": "QUALITY_HARD_FAIL: stop_words found",
  "dlq_reason": "QUALITY_HARD_FAIL"
}

Значения output_type:

output_type Значение NocoDB статус
success Текст прошел все проверки published
forward_phase6 Мягкие проверки не пройдены draft
dlq Критическая ошибка failed

Важно: Этот вызов синхронный — ожидание ответа занимает 2-5 минут. Для асинхронной обработки используйте webhook (см. ниже).


DELETE /api/v1/category-generate/idempotency/{task_index}

Сброс ключа идемпотентности для задачи. После сброса задачу можно повторно обработать через webhook.

curl -X DELETE http://localhost:8001/api/v1/category-generate/idempotency/borsh-001 \
  -H "X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a"

Ответ (200):

{
  "task_index": "borsh-001",
  "key": "seo:cat:borsh-001",
  "deleted": true
}

Если ключа не было (задача не обрабатывалась или TTL истёк): "deleted": false.


POST /api/v1/category-parse

Парсинг страницы категории на povar.ru. Возвращает название, список рецептов и текущий SEO-текст (если есть).

curl -X POST "http://localhost:8001/api/v1/category-parse?category_url=https://povar.ru/list/borsh/" \
  -H "X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a"

Примечание: category_url передается как query-параметр (в URL), не в теле запроса.

Ответ (200):

{
  "category_name": "Борщ",
  "category_url": "https://povar.ru/list/borsh/",
  "recipe_titles": ["Украинский борщ", "Борщ с фасолью", "Борщ с капустой"],
  "recipe_count": 49,
  "current_seo_text": null
}

POST /api/v1/tasks/import-csv

Импорт задач из XLSX или CSV файла в NocoDB таблицу seo_tasks.

curl -X POST http://localhost:8001/api/v1/tasks/import-csv \
  -H "X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a" \
  -F "file=@tasks.xlsx"

Query-параметры:

Параметр Тип По умолчанию Описание
deltas_mode boolean false Если true, значения target_chars_* трактуются как дельты от конкурентов. Применяется abs() перед валидацией.

Ответ (200):

{
  "total_rows": 12,
  "imported": 10,
  "skipped": 0,
  "errors": [
    {
      "row": 3,
      "task_index": "task-003",
      "error": "target_chars_txt=300 < 500 (minimum for pipeline)"
    }
  ],
  "warnings": [
    {
      "row": 5,
      "task_index": "task-005",
      "warning": "competitor_urls is empty"
    }
  ]
}

Принимаемые форматы файлов: .xlsx (основной), .csv (запасной, кодировки UTF-8 или Windows-1251).


POST /api/v1/tasks/start-generation

Запуск пакетной генерации для всех задач со статусом pending. Внутренне вызывает webhook seo-category-batch в n8n. Этот эндпоинт используется кнопкой «Запустить генерацию» в форме импорта.

curl -X POST http://localhost:8001/api/v1/tasks/start-generation \
  -H "X-API-Key: 6dba6e9e4688bcc60c43720d828ee5e5a2e443e7559b83197fa2f77a0510831a"

Ответ (200):

{"message": "Workflow was started"}

Обработка идёт в фоне. Результаты — в Telegram и NocoDB.


Webhook (n8n)

POST https://n8n.pyp.ru/webhook/seo-category-v1

Асинхронный запуск генерации через n8n workflow. Webhook принимает задачу, сразу отвечает 202 и обрабатывает в фоне. Результат приходит в Telegram и сохраняется в NocoDB.

curl -X POST https://n8n.pyp.ru/webhook/seo-category-v1 \
  -H "Content-Type: application/json" \
  -d '{
    "task_index": "borsh-001",
    "category_url": "https://povar.ru/list/borsh/",
    "category_name": "Борщ",
    "target_chars_no_spaces": 2000
  }'

Параметры (JSON body):

Поле Тип Обязательное Описание
task_index строка Да Уникальный идентификатор
category_url строка Да URL категории (полный или путь /list/...)
category_name строка Нет Название категории
category_type строка Нет list (по умолчанию) или menu
target_chars_no_spaces число Рекомендуется Целевой объем текста (допустимый диапазон 1800-2200, рекомендуется 2000)

Важно: Всегда указывайте target_chars_no_spaces при вызове webhook. Если поле пропущено, используется значение по умолчанию, которое может вызвать ошибку валидации.

Коды ответа:

Код Значение Пример ответа
202 Задача принята, обработка запущена {"status": "accepted", "task_index": "borsh-001"}
400 Ошибка валидации {"status": "error", "error": "Missing or invalid category_url"}
409 Задача уже обрабатывалась {"status": "duplicate", "task_index": "borsh-001", "reason": "Already processed"}

Важно: Webhook работает асинхронно. Ответ 202 означает только принятие задачи. Фактическая генерация занимает 2-5 минут. Результат — в Telegram и NocoDB.


POST https://n8n.pyp.ru/webhook/seo-category-batch

Запуск пакетной генерации для всех задач со статусом pending в NocoDB. Webhook отвечает мгновенно, обработка идёт в фоне. Тело запроса не требуется.

curl -X POST https://n8n.pyp.ru/webhook/seo-category-batch

Ответ (200, мгновенный):

{"message": "Workflow was started"}

Workflow автоматически: 1. Читает из NocoDB seo_tasks записи с status=pending и заполненным category_url (до 50 штук) 2. Для каждой задачи: помечает processing → вызывает Python API → обновляет статус → Telegram 3. Повторный запуск подхватит следующую порцию задач

Примечание: Этот же webhook вызывается кнопкой «Запустить генерацию» в форме импорта (/api/v1/tasks/import) и эндпоинтом POST /api/v1/tasks/start-generation.


Коды ответов (сводка)

HTTP код Значение Когда возникает Действие
200 OK Успешный запрос
202 Accepted Webhook принял задачу Ждать результат в Telegram / NocoDB
400 Bad Request Невалидные данные Проверить обязательные поля
401 Unauthorized Неверный или отсутствующий API-ключ Проверить заголовок X-API-Key
409 Conflict Идемпотентность (задача уже обработана) Использовать ?force=true или DELETE idempotency
422 Validation Error Поля не прошли валидацию Проверить типы и диапазоны полей
429 Too Many Requests Превышен лимит запросов (60/мин) Подождать, повторить позже
500 Internal Error Ошибка сервера Проверить логи: docker logs seo-python-service --tail 50
502 Bad Gateway n8n вернул ошибку Проверить n8n UI: https://n8n.pyp.ru
503 Service Unavailable Зависимый сервис недоступен Проверить Redis, NocoDB, n8n

NocoDB: таблицы

seo_tasks (ID: m3bxdcm1daw4h9l)

Входные задачи для генерации.

Поле Тип Описание
task_index Text Уникальный идентификатор задачи
search_query Text Поисковый запрос (используется как category_name при webhook)
category_url Text URL категории на povar.ru
category_type Text Тип категории: list или menu
lemmas_txt LongText JSON-массив лемм для основного текста
target_chars_txt Number Целевой объем текста в символах
status Select Текущий статус задачи
retry_count Number Количество попыток (по умолчанию 0)
last_error LongText Последняя ошибка (если retry)

Статусы задач:

Статус Описание
pending Ожидает обработки (batch workflow подхватит)
processing В процессе генерации
generated Успешно сгенерировано
draft Текст создан, требует ручной проверки
failed Ошибка генерации

generated_content (ID: mnigro30b4v0dys)

Результаты генерации. Записи создаются автоматически после каждой генерации.

Поле Тип Описание
task_index Text Идентификатор задачи (связь с seo_tasks)
category_url Text URL обработанной категории
zone_h1 Text Заголовок H1
zone_top LongText SEO-текст над рецептами (~400 символов)
zone_txt LongText Основной SEO-текст (~2000 символов)
zone_t Text Meta title
meta_description Text Meta description
output_type Text Тип результата: success, forward_phase6, dlq
status Text Статус: published, draft, failed
chars_no_spaces Number Символов без пробелов в zone_txt
recipe_count Number Количество рецептов на странице
iterations Number Количество итераций генерации
warnings LongText JSON-массив предупреждений
content_type Text Тип контента (всегда category)
uniqueness_pct Number Уникальность текста в % (text.ru Antiplagiat, v7.0)
textru_uid Text UID проверки на text.ru (для перепроверки)
textru_status Text Статус text.ru: passed, marginal, failed, skipped, error

dlq_errors (ID: mtkthgqytw21afd)

Ошибки генерации. Записи создаются автоматически при критических сбоях.

Поле Тип Описание
error_type Select Тип ошибки (см. таблицу ниже)
error_code Text task_index задачи
error_message LongText Полное описание ошибки
error_stack LongText Технические детали (JSON)
attempts Number Количество попыток
resolved Boolean Решена ли проблема

Типы ошибок:

Тип Описание Что делать
API_ERROR Ошибка LLM-провайдера (OpenRouter) Подождать 5 мин (circuit breaker), перегенерировать
CB_OPEN Circuit breaker заблокировал запрос Подождать 5 мин, запросы пойдут автоматически
VALIDATION_ERROR Ошибка валидации данных Проверить входные данные задачи
QUALITY_HARD_FAIL Текст не прошел критические проверки Перегенерировать с ?force=true
TEXTRU_QUALITY_FAIL Уникальность текста <85% (text.ru Antiplagiat) Перегенерировать с ?force=true
TIMEOUT_ERROR Таймаут LLM-вызова (>90 сек) Перегенерировать (обычно временная проблема)
ENDPOINT_ERROR Необработанная ошибка сервера Проверить логи сервиса

Проверки качества (16 штук)

Система выполняет 16 автоматических проверок после каждой генерации. Проверки делятся на критические (hard fail) и мягкие (soft fail).

Критические проверки (hard fail)

Если любая из этих проверок не пройдена, текст возвращается Редактору на исправление (до 4 итераций). Если после 4 итераций проблема не решена, задача отправляется в DLQ.

Проверка Порог Что проверяет
Стоп-слова (AI-маркеры) 0 найденных Типичные слова, выдающие AI-генерацию
Объем zone_txt 1800-2200 символов Основной текст в допустимом диапазоне
Длина zone_top 290-470 символов SEO-текст над рецептами в допустимом диапазоне
Первое лицо ед.ч. 0 маркеров Нет слов "я", "мой", "расскажу" и т.д.
Тавтология 0 повторов Нет одинаковых слов в соседних предложениях
Начало с вопроса Нет Текст не начинается с вопросительного предложения
Паттерны рецепта 0 нарушений Текст не похож на рецепт (нет "Шаг 1", "200 г", "3 ст.л.")
SEO-фразы zone_txt 80% и выше Покрытие SEO-фраз в основном тексте

Мягкие проверки (soft fail)

Если не пройдены, текст получает статус draft (forward_phase6). Обычно пригоден к публикации.

Проверка Порог Что проверяет
Burstiness (разнообразие) 0.10 и выше Разброс длины предложений (чем выше — тем живее текст)
Частицы 2.0/1000 символов и выше Наличие частиц "же", "ведь", "ли" (признак живого текста)
Леммы 70% и выше Покрытие заданных ключевых слов
Тире 5/1000 символов и менее Плотность тире (чрезмерное — признак AI)
Буква ё 0 Все ё заменены на е
Идеальная длина zone_top 350-450 символов Оптимальный диапазон для SEO
Числа рецептов 0 Нет конкретных чисел рецептов в тексте ("35 рецептов")
SEO-фразы zone_top 70% и выше Покрытие SEO-фраз в тексте над рецептами

Конфигурация (.env)

Параметры, которые можно изменить при необходимости. Файл .env находится в /opt/seo-python-service/.env.

Параметры для оператора

Переменная Текущее значение Описание
OPENROUTER_DEFAULT_MODEL anthropic/claude-sonnet-4.5 LLM-модель для генерации текста
WRITER_TEMPERATURE 0.8 Температура Генератора (0.0-1.5, выше = креативнее)
EDITOR_TEMPERATURE 0.4 Температура Редактора (0.0-1.5, ниже = точнее)
BURSTINESS_THRESHOLD 0.10 Минимальный порог разнообразия длины предложений
TELEGRAM_ALERT_COOLDOWN 300 Пауза между одинаковыми уведомлениями (секунды)
TEXTRU_ENABLED true Включить/выключить проверку text.ru (v7.0)
TEXTRU_AI_ENABLED false Включить AI-детекцию (требует нейросимволы на text.ru)
TEXTRU_UNIQUENESS_PASS 95.0 Порог уникальности для published (%)
TEXTRU_UNIQUENESS_MARGINAL 85.0 Порог уникальности для draft (%, ниже = failed)

Как применить изменения

  1. Подключитесь к серверу: ssh root@88.99.12.74
  2. Отредактируйте файл: nano /opt/seo-python-service/.env
  3. Перезапустите сервис:
cd /opt/seo-python-service
docker compose -f docker-compose.seo.yml up -d --force-recreate
  1. Проверьте работоспособность: curl http://localhost:8001/health

Версии компонентов

Компонент Версия Назначение
Python Service v7.1 SEO-генерация текстов + проверка уникальности text.ru
n8n 2.12.3 Оркестрация воркфлоу
NocoDB 0.301.5 База данных задач и результатов
Redis (Valkey) 8.1.6 Кэш, очереди, идемпотентность
PostgreSQL 17.9 Хранилище данных n8n и Langfuse
Crawl4ai Парсинг страниц povar.ru
Caddy Reverse proxy (n8n.pyp.ru)
LLM Claude Sonnet 4.5 Языковая модель (через OpenRouter)
PydanticAI 1.70.0 AI-фреймворк для агентов
Docker network n8n-install_default Внутренняя сеть контейнеров