Справочник API: SEO-генератор
Версия: v7.1 | Сервер: pyp.ru (88.99.12.74) | Дата: 2026-03-24
Подключение
Базовый URL: http://localhost:8001 (на сервере)
Аутентификация: Заголовок X-API-Key во всех запросах (кроме /health).
Формат ошибок: Все ошибки возвращаются в формате JSON:
Эндпоинты
GET /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):
Если ключа не было (задача не обрабатывалась или 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):
Обработка идёт в фоне. Результаты — в 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 отвечает мгновенно, обработка идёт в фоне. Тело запроса не требуется.
Ответ (200, мгновенный):
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) |
Как применить изменения
- Подключитесь к серверу:
ssh root@88.99.12.74 - Отредактируйте файл:
nano /opt/seo-python-service/.env - Перезапустите сервис:
- Проверьте работоспособность:
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 | Внутренняя сеть контейнеров |