Документация API
Зачем это нужно
Seolity.ru анализирует текстовую релевантность страницы поисковому запросу. Сервис парсит ТОП Яндекса по запросу, разбирает тексты конкурентов и сравнивает с вашей страницей.
API отдаёт структурированные данные этого анализа. Задача AI-агента - использовать эти данные для оптимизации исходного кода страницы:
- Убрать переспам - слова, которых на странице значительно больше, чем у конкурентов.
- Добавить недостающие тематические слова - слова, которые есть у большинства конкурентов, но отсутствуют на нашей странице.
- Усилить недобор - слова, которые есть на странице, но в недостаточном количестве. Довести до медианы по конкурентам.
- Не трогать слова, где текущее значение ≈ медиане - они уже в норме.
Важно: работать нужно с исходным кодом HTML страницы. Цель - не механически набить ключевые слова, а естественно вписать их в полезный контент для пользователя.
Авторизация
Все запросы требуют токен в заголовке Authorization:
curl -H "Authorization: Bearer <ваш_auth_key>" \ https://seolity.ru/api/analysis/<id>/
Токен - это ваш API-ключ. Получить его можно в личном кабинете на странице API.
API доступен на платном тарифе. Все адреса - со слешем на конце (без него сервер вернёт редирект 301).
Ключ принимается только заголовком. Параметр ?token= в адресе не принимается: адреса запросов оседают в логах серверов и прокси. Запрос с ?token= и без заголовка получит 401 с подсказкой, как передать ключ правильно.
Ответы, которые меняются со временем, содержат поле apiVersion (дата версии формата, сейчас 2026.10.08). Поля добавляются, старые не исчезают; увидели новую версию - перечитайте блок _instruction в ответе.
Endpoints
| Endpoint | Описание |
|---|---|
| GET /api/analysis/<id>/ | Полный отчёт по релевантности страницы (~40–50 КБ JSON) |
| GET /api/analysis/<id>/status/ | Готов ли анализ: queued / parsing / done / error |
| GET /api/analysis/<id>/history/ | Все анализы той же пары по времени с дельтами метрик |
| GET /api/analysis-brief/<id>/ | Краткий: только метрики + рекомендации |
| GET /api/analysis/<id>/tz/ | Готовое ТЗ копирайтеру по одному анализу (Markdown) |
| GET /api/run/<id>/tz/ | ТЗ по пакетному анализу (прогону): постранично или выборкой ?ids |
| POST /api/run/<id>/tz/build/ | Запустить фоновую сборку всех ТЗ прогона |
| GET /api/run/<id>/tz/status/ | Прогресс фоновой сборки ТЗ |
| GET /api/run/<id>/tz-zip/ | Архив ZIP со всеми готовыми ТЗ прогона |
| GET /api/run/<id>/structure/ | Отчёт «Анализ шаблона сайта» по прогону (JSON) |
| GET /api/cross-analysis/<id>/ | Кросс-анализ группы страниц (JSON) |
| GET /api/clustering/<id>/ | Кластеризация: сырьё прогона (кластеры, частотности) |
| GET /api/clustering/<id>/?domain=&only=positions | Кластеризация: позиции вашего домена по каждой фразе |
| GET /api/clustering/<id>/status/ | Кластеризация: статус, процент, оценка времени до готовности |
| GET /api/clustering/<id>/report/ | Кластеризация: карта страниц (summary + hubs) |
| GET /api/clustering/<id>/competitors/?domains= | Кластеризация: группировка по страницам конкурента |
| GET|POST /api/clustering/estimate/ | Смета кластеризации без запуска и без списания |
| GET /api/balance/ | Бонусный баланс и срок подписки аккаунта |
| GET /api/run/<id>/pairs/ | Список пар «запрос → страница» прогона с их public_id и статус прогона |
| GET /api/run/<id>/analysis/?ids= | Полные анализы пар одним ответом, с выбором разделов |
| GET|POST /api/run/estimate/ | Смета запуска анализа: юниты, повторы, лимиты |
| POST /api/run/ | Запуск анализа (одна пара или пакет) |
| POST /api/run/<id>/recalc/ | Пересчёт готовых пар на тех же конкурентах (dry_run=true - смета) |
| GET /api/run/<id>/diff/ | «Было/стало» по парам двух прогонов |
| GET /api/usage/ | Журнал запусков через API и остатки на сегодня |
<id> анализа - токен из URL страницы результата: seolity.ru/analysis/result/?id=aBcdEf12. <id> прогона - токен из URL прогона: seolity.ru/project/run/aBcdEf12/ (виден и в ссылках «Анализ шаблона»).
Структура полного отчёта
_instruction
Встроенная инструкция с описанием данных и целей. AI-агент должен читать её первой.
analysis
Метаданные анализа:
| Поле | Описание |
|---|---|
| query | Поисковый запрос |
| url | Анализируемая страница |
| normalized | Включена ли нормировка |
| normCoeff | Коэффициент нормировки (слов_моих / медиана_слов_конкурентов) |
myPage
Метрики анализируемой страницы:
| Поле | Описание |
|---|---|
| countWords | Количество слов на странице |
| width | Ширина (0-100): % значимых слов тематики, присутствующих на странице. |
| depth | Глубина (0-100): насколько частоты слов близки к медианам конкурентов. |
| overall | (Ширина + Глубина) / 2 минус штраф за title. Общий показатель релевантности. |
| titlePenalty | Штраф за отсутствие слов запроса в теге <title> |
| bm25, bm25Rank | BM25-релевантность и ранг среди всех документов |
| queryCoverage | Покрытие слов запроса: для каждого слова - сколько раз оно встречается |
competitorsMedian
Медианы по конкурентам: countWords, hCount (заголовки), aCount (ссылки).
recommendations
Готовые рекомендации - начинать нужно с них:
| Поле | Описание |
|---|---|
| severity | high / medium / low - приоритет |
| type | missing, underused, overspam, query_coverage, length_short/length_long |
| word | Какое слово |
| now | Сколько сейчас на странице |
| target | К чему стремиться (число или диапазон «X–Y») |
| detail | Пояснение |
widthMissing
Слова из корпуса конкурентов, которых нет на странице. Отсортированы по важности. Добавление увеличит показатель Width.
| Поле | Описание |
|---|---|
| word | Слово (лемма) |
| median | Медиана вхождений у конкурентов |
| sites | На скольких сайтах ТОП встречается |
depthIssues
Слова, где частота далека от оптимальной. Исправление увеличит показатель Depth.
| Поле | Описание |
|---|---|
| word | Слово |
| mine | Сколько раз на странице |
| median | Нормированная медиана конкурентов (цель) |
| score | Depth score 0..1 (чем ниже, тем хуже) |
| issue | missing, under, overspam |
| delta | Разница (median − mine). Положительный = добавить, отрицательный = убрать |
words
Полная таблица слов (топ-200 по значимости):
| Поле | Описание |
|---|---|
| w | Слово (лемма, самая частая словоформа) |
| s | На скольких сайтах ТОП встречается |
| med | Медиана по конкурентам |
| avg | Среднее по конкурентам |
| mine | Сколько раз на странице (абсолютное число) |
| d | Дельта (med − mine) |
| medT / mineT | Текст (body без анкоров): медиана / мой |
| medA / mineA | Анкоры (текст в <a>): медиана / мой |
ngrams
Устойчивые выражения (биграммы и триграммы):
| Поле | Описание |
|---|---|
| phrase | Фраза |
| n | 2 (биграмма) или 3 (триграмма) |
| sites | На скольких сайтах ТОП |
| med | Медиана повторов |
| mine | Сколько раз на странице |
| d | Дельта |
topRelevance
Таблица конкурентов с метриками (width, depth, overall, bm25, countWords).
Нормировка
Если analysis.normalized = true, медианы масштабированы под длину страницы:
коэфф = слов_на_моей_странице / медиана_слов_конкурентов нормированная_медиана = абсолютная_медиана × коэфф
Поле mine - всегда абсолютное (реальное количество на странице).
Как слова соотносятся с исходным кодом
- Леммы: слова в таблице - базовые формы. На странице могут быть в любой словоформе.
- Текст (
medT/mineT) - слова в body без учёта текста ссылок. - Анкоры (
medA/mineA) - текст внутри тегов<a>. - N-граммы - устойчивые выражения. Улучшают тематическую релевантность больше, чем отдельные слова.
ТЗ копирайтеру (Markdown)
Помимо сырых данных, API отдаёт готовое техническое задание на текст - по одному анализу или по всему пакетному анализу (прогону).
Одна страница: GET /api/analysis/<id>/tz/
{
"query": "поисковый запрос",
"url": "https://site.ru/stranica/",
"public_id": "aBcdEf12",
"tz_markdown": "# ТЗ на текст ..."
}
Если исходные данные анализа уже вычищены, вернётся {"error":"no_data"} - нужен повторный прогон (rerun).
Пакетный анализ: GET /api/run/<id>/tz/
Два режима:
- Без параметров - постранично отдаёт уже собранные ТЗ прогона (дёшево, без пересборки). Параметры:
?offset=0&limit=50(limit до 200). ?ids=aBc,dEf- синхронно собрать ТЗ по выбранным парам. Недостроенное попадёт в массивskippedс причиной.
{
"run": "aBcdEf12",
"total_built": 120,
"offset": 0, "limit": 50, "returned": 50,
"has_more": true,
"stats": { ...прогресс сборки... },
"items": [ { "query": "...", "url": "...", "public_id": "...", "tz_markdown": "..." } ]
}
Масштаб: фоновая сборка
Для больших прогонов сборку запускают в фоне, затем забирают результат:
POST /api/run/<id>/tz/build/- запустить сборку всех ТЗ (?force=1- пересобрать заново).GET /api/run/<id>/tz/status/- прогресс (сколько готово из общего числа).GET /api/run/<id>/tz/- листать готовые ТЗ (см. выше).GET /api/run/<id>/tz-zip/- скачать всё архивом ZIP (по .md на пару +manifest.jsonсо статусом каждой пары; можно качать даже частично готовым).
Анализ шаблона сайта: GET /api/run/<id>/structure/
Сравнивает шаблон вашего сайта с шаблоном ТОП-конкурентов (костяк) по прогону: каких сквозных блоков, разделов меню и слов не хватает и как вы ранжируетесь относительно костяка. Везде поле gap: true означает «есть в шаблоне у большинства ТОП, но отсутствует у вас» - кандидат на внедрение.
{
"ok": true,
"report_url": "https://seolity.ru/structure-poc/run/?id=aBcdEf12",
"run_id": "aBcdEf12",
"domain": "site.ru",
"query_count": 29,
"core_competitors_count": 12,
"position": { "verdict": "leader|behind", "summary": "...",
"my_in_top": 22, "my_avg_pos": 7.0, "core_avg_pos": 6.4 },
"template_vs_content": {
"mine": { "template_ratio_pct": 63.0, "avg_unique_content_words": 1130, "template_link_count": 180 },
"core_avg": { "template_ratio_pct": 68.0, "avg_unique_content_words": 900, "template_link_count": 210 }
},
"functional_blocks": [ { "block": "faq", "label": "FAQ / вопросы-ответы",
"top_pct": 75, "we_have": false, "gap": true } ],
"heading_section_gaps": [ { "term": "...", "top": 8, "top_pct": 66 } ],
"menu_link_gaps": [ { "anchor": "...", "top": 9, "top_pct": 75, "example_href": "/..." } ],
"template_words": {
"core_count": 12, "gaps_count": 48,
"gaps": [ { "word": "...", "consensus": 11, "pct": 92 } ],
"we_have_sample": [ { "word": "...", "consensus": 12 } ]
}
}
| Раздел ответа | Что показывает |
|---|---|
| position | Реальная позиция вашего сайта в выдаче относительно костяка: лидер / отстаёте, по скольким запросам в ТОПе, средние позиции. |
| template_vs_content | Доля шаблона против уникального контента у вас и в среднем по костяку. |
| functional_blocks | Сквозные функциональные блоки (цены, характеристики, FAQ, отзывы, формы, гарантии и т.д.): есть ли у вас и у большинства ТОП. |
| heading_section_gaps | Тематические секции в заголовках ТОП, которых нет у вас. |
| menu_link_gaps | Сквозные разделы меню/подвала, которые держат конкуренты, а вы нет. |
| template_words | Сквозные слова тела страниц: gaps - которых нет в вашем шаблоне (кандидаты в сквозной текст/перелинковку); consensus - у скольких из core_count сайтов ТОП слово сквозное. |
Кросс-анализ: GET /api/cross-analysis/<id>/
Сравнивает группу ваших страниц между собой и находит сквозные текстовые проблемы - то, что повторяется на многих страницах и чинится один раз в шаблоне. <id> - токен из URL кросс-анализа: seolity.ru/cross-analysis/view/aBcdEf12/.
{
"ok": true,
"report_url": "https://seolity.ru/cross-analysis/view/aBcdEf12/",
"id": "aBcdEf12",
"name": "site.ru - 16 стр.",
"summary": { "total_pages": 16, "avg_offtopic_pct": 34.9, "avg_overall": 68,
"common_overspam_words": 81, "common_suspicious_sentences": 18,
"template_sentence_count": 6 },
"pages": [ { "query": "...", "url": "...", "public_id": "...",
"width": 80, "depth": 70, "overall": 75, "count_words": 1200,
"offtopic_pct": 30, "offtopic_words": 40,
"overspam_count": 12, "overspam_occurrences": 90, "suspicious_count": 5 } ],
"common_overspam": [ { "word": "деликатный", "pages": 16, "total_occurrences": 264 } ],
"common_suspicious": [ { "text": "...", "pages": 8, "rare_pct": 60 } ],
"template_boilerplate": [ { "text": "...", "words": 12 } ]
}
| Раздел ответа | Что показывает |
|---|---|
| common_overspam | Слова, переспамленные сразу на нескольких страницах (pages - на скольких, total_occurrences - суммарно). Почти всегда это шаблонный мусор - правится один раз в шаблоне. |
| common_suspicious | Мусорные/нетематические фразы, повторяющиеся на нескольких страницах (rare_pct - доля редких слов). |
| template_boilerplate | Предложения, дублирующиеся на всех страницах - сквозной текст шаблона. |
| pages | Метрики каждой страницы (width/depth/overall 0–100, offtopic_pct - доля непрофильного текста). |
Приоритет правок: сначала общее (common_overspam / common_suspicious / template_boilerplate) - эффект на весь сайт, потом постраничное.
Кластеризация
API отдаёт готовый результат кластеризации семантического ядра: кластеры (потенциальные страницы), карту страниц и группировку запросов по страницам конкурента.
Запуск кластеризации - в кабинете (кабинет → Кластеризация → загрузить запросы → запустить). Ключ клиента может посчитать смету (/api/clustering/estimate/), следить за готовностью (/status/) и читать результат. <id> - токен из URL результата: seolity.ru/clustering/result/aBcdEf12/.
| Endpoint | Описание |
|---|---|
| GET /api/clustering/<id>/ | Сырьё прогона: кластеры, частотности, домен-статистика |
| GET /api/clustering/<id>/report/ | Карта страниц: сводка + хабы (потенциальные страницы) |
| GET /api/clustering/<id>/competitors/?domains= | Группировка запросов по страницам конкурента + пробелы вашего сайта |
GET /api/clustering/<id>/ - сырьё прогона
| Поле | Описание |
|---|---|
| clusters | Кластеры уровней grp1-4 (иерархия групп запросов) |
| clusters_google | Кластеры по Google - только в режиме «Оба поисковика» |
| частотности | Собранная частотность по запросам (базовая / фразовая / точная - в зависимости от настроек прогона) |
| домен-статистика | Статистика по доменам из собранной выдачи |
GET /api/clustering/<id>/report/ - карта страниц
Отдаёт summary (сводка по прогону) и массив hubs[] - по одной потенциальной странице на элемент:
| Поле хаба | Описание |
|---|---|
| title | Название потенциальной страницы |
| action | new / expand / push / strengthen / defend - что делать со страницей |
| totalExact / totalBase | Суммарная точная / базовая частотность запросов хаба |
| landingUrl | Найденная целевая страница вашего сайта и распределение позиций по запросам |
| consensus | Сигнал «Google дробит хаб» (консенсус двух прогонов) |
| commercial[] / faq[] | Запросы хаба: коммерческие / информационные (FAQ) |
| seasonality | Профиль спроса, пик и годовой тренд по хабу |
GET /api/clustering/<id>/competitors/?domains=host1,host2
Группирует запросы прогона по страницам конкурента и показывает пробелы вашего сайта против него.
| Поле | Описание |
|---|---|
| domains[] | Домены конкурентов, по которым сделана группировка (из параметра ?domains=) |
| clusters[] | Группы запросов по страницам конкурента |
| uncovered[] | Запросы, которые конкурент не закрывает |
Позиции вашего домена: GET /api/clustering/<id>/?domain=site.ru&only=positions
Выдача, снятая при кластеризации, хранится целиком, поэтому по ней можно узнать позицию вашего сайта по каждой фразе без отдельного съёма позиций. Параметр domain добавляет к каждой фразе поле positions, а only=positions убирает из ответа кластеры, статистику доменов и списки URL выдачи - остаётся лёгкий ответ только с позициями.
Поле queries[].positions | Описание |
|---|---|
| found | Нашёлся ли домен в выдаче по фразе |
| position | Лучшая позиция любого хоста домена (1 - первое место) |
| url / host | Адрес и хост, который занял эту позицию |
| hosts[] | Первое вхождение каждого хоста домена: поддомены считаются отдельно, www. от основного домена не отличается |
| top_n | Глубина снятой выдачи |
| serp_status | ok - выдача есть; empty - выдача не пришла; pending - фраза ещё не снята |
Сводка по всем фразам - в positions_summary: queries, found, not_found, by_host (сколько фраз занял каждый хост). Дата съёма - project.finished_at. Параметр serp=0 убирает списки URL выдачи, не трогая остальное.
Позиции отдаются для прогонов до 5000 фраз и только без постраничного режима (без limit, offset, grp1, min_exact). У больших прогонов ответ постраничный, и поле positions в нём нет.
curl -sL -H "Authorization: Bearer <ваш_токен>" \ "https://seolity.ru/api/clustering/aBcdEf12/?domain=site.ru&only=positions"
Готовность прогона: GET /api/clustering/<id>/status/
Лёгкий ответ для опроса, пока прогон идёт. Результат читать после status=done.
| Поле | Описание |
|---|---|
| status | created / parsing (снимается выдача) / clustering (собираются кластеры) / done / error / pending_payment (ждёт оплаты) |
| queries | total, done, error, pending - сколько фраз снято и сколько осталось |
| percent | Доля снятых фраз; после 100% идёт фаза clustering, обычно несколько минут |
| eta_minutes | Оценка минут до готовности по фактической скорости съёма плюс повторы пустых выдач и финальная сборка. null - пока не снята ни одна фраза, оценить нельзя |
| rate_per_minute / elapsed_minutes | Скорость съёма (фраз в минуту) и сколько прошло с начала |
| retries_pending | Есть фразы, по которым выдача пришла пустой и будет запрошена повторно |
| payment_status, created_at, finished_at | Оплата и даты прогона |
Смета кластеризации: GET|POST /api/clustering/estimate/
Считает стоимость прогона по той же формуле, что кабинет, ничего не создавая и не списывая. Доступна любому ключу.
| Параметр | Описание |
|---|---|
| queries[] или query_count | Список фраз (проходит ту же чистку, отбраковку и удаление дублей, что при запуске; clean=false - без чистки) или просто их число |
| topN | Глубина выдачи: 10, 20 (по умолчанию), 30 или 50 |
| engine | yandex (по умолчанию), google или both |
| freqTypes[] | Какие частотности собирать (типы перечислены в ошибке, если передать неизвестный) |
| seasonality, seasonalityYears | Сезонность и за сколько лет; при сезонности базовая частотность идёт бонусом |
В ответе: query_count (сколько фраз останется после чистки) и query_count_raw (сколько прислали), cost с разбивкой (serp, frequency, seasonality, total в рублях), balance (bonus_balance, bonus_after, to_pay_card - сколько доплатить картой, если направить бонусы). Регион и домен на цену не влияют. Неверные параметры - 400 со списком errors.
Баланс аккаунта: GET /api/balance/
Что есть на аккаунте для оплаты прогонов кластеризации: bonus_balance (бонусный баланс в рублях), subscription_until (до какой даты действует подписка), account. Денежного кошелька нет: платный прогон кластеризации оплачивается картой при создании в кабинете, бонусы можно направить в счёт оплаты. Остатки юнитов и кредитов для анализов показывает GET /api/usage/.
Пары прогона и пакетные анализы
Идентификаторы разные: у прогона свой <run_id>, у каждой пары «запрос → страница» - свой public_id. Полный анализ открывается только по public_id пары; если подставить в него id прогона, ответ прямо об этом скажет и подскажет нужный эндпоинт.
| Endpoint | Что отдаёт |
|---|---|
| GET /api/run/<run_id>/pairs/ | Все пары прогона: public_id, запрос, URL, статус, ссылка на отчёт. Плюс статус всего прогона (см. ниже) |
| GET /api/run/<run_id>/analysis/?ids=id1,id2 | Полные анализы пар одним ответом. Без ids - все готовые пары прогона |
Полный отчёт одной страницы весит до полутора мегабайт, поэтому пакетный эндпоинт по умолчанию отдаёт рабочий набор разделов: настройки, объёмы, сводку, рекомендации, переспам, подозрительные предложения, обязательные слова и Title. Нужен свой набор - перечислите разделы в fields=; нужен отчёт целиком (вместе с таблицами words и widthRecommended) - full=1. Что не вошло, перечислено в _omittedFields.
Статус прогона в /pairs/
Этот же эндпоинт служит для ожидания: опрашивайте его раз в 1-2 минуты, пока finished не станет true. Статусы строк обновляются раз в минуту, поэтому отставание до минуты нормально.
| Поле | Описание |
|---|---|
| run_status | pending / running / done |
| finished | true, когда прогон завершён целиком |
| counts | Сколько пар в каждом статусе: queued, parsing, done, error |
| percent | Доля завершённых пар (готовых и с ошибкой) |
| eta_minutes | Грубая оценка минут до конца: пары идут волнами по 15, волна около 4 минут. Оценка, не обещание |
| total / ready | Всего пар и сколько из них можно читать |
| pairs[] | public_id, query, url, status, ready, error, analysis_endpoint, report_url |
Один анализ: GET /api/analysis/<id>/status/
Лёгкий статус одной пары по её public_id: status (queued / parsing / done / error), ready, created_at и подсказка next, что делать дальше. Опрашивайте до ready=true, потом читайте полный отчёт. При error анализ нужно запустить заново.
История пары: GET /api/analysis/<id>/history/
Все анализы той же пары у вашего аккаунта по времени, от старых к новым. Пара - это запрос + страница + поисковик + регион + глубина ТОПа (и режим noindex): Яндекс и Google разные ТОПы, их точки в одну серию не смешиваются.
| Поле | Описание |
|---|---|
| pair | query, url, html_mode, searchEngine, region, topN |
| points[] | Точки серии: public_id, date, kind, metrics (width, depth, overall, bm25, countWords, textOnlyWords, anchorWords), is_requested (точка, по которой спросили) |
| points[].kind | fresh_serp - по свежей выдаче, в дельту входит движение ТОПа; recalc_same_competitors - пересчёт на тех же конкурентах, дельта от правок страницы; custom_competitors - свой список конкурентов |
| points[].delta_prev / delta_first | Изменение width, depth, overall, countWords к предыдущей точке и к первой |
| points_total, truncated | Сколько точек; при truncated=true отданы последние 200 |
metrics=null - отчёт этой точки ещё не прогревался: откройте GET /api/analysis/<public_id>/ и повторите запрос.
Настройки прогона и объём текста
В ответе полного анализа есть два блока, по которым видно, как именно посчитан отчёт.
| Поле | Описание |
|---|---|
| settings.countNoindexText | false - текст в <noindex> и в комментарной разметке <!--noindex--> исключён из расчёта |
| settings.stripBoilerplate | Меню, подвал и сквозные блоки в расчёт не входят независимо от настроек |
| settings.topN / normalizeDensity / mode404 … | Остальные параметры прогона, влияющие на цифры |
| wordCounts.total | Весь текст страницы |
| wordCounts.indexable | Вне noindex (учитываются обе формы разметки) |
| wordCounts.noindexClosed | Сколько слов закрыто от индексации |
| wordCounts.analyzed | Сколько слов вошло в расчёт: вне noindex, без обвязки и служебных слов |
| summary.volume | Объём против медианы ТОПа и что с ним делать: shorten / keep / extend, рекомендуемый объём |
| *.medianRaw, overspamWords[].topMedianRaw | Сырая медиана ТОПа рядом с нормализованной: цели в отчёте масштабированы под текущую длину страницы, и при её сокращении опираться нужно на сырые значения |
Запуск анализов и пересчёт через API
Ключ платного тарифа может сам запускать анализ релевантности и пересчитывать его после правки страницы. Так ИИ-агент проходит весь цикл без кабинета: анализ → чистка и добор текста → пересчёт на тех же конкурентах → «было/стало». Ключ без активной подписки (только поштучные кредиты) в API не пускается: 403.
Сколько стоит
Цена та же, что в кабинете, и списывается тем же кодом в момент старта строки:
| Что | Цена |
|---|---|
Строка анализа (POST /api/run/) | 1 юнит пула тарифа. Сверх дневного лимита тарифа (Джун 5, Мидл 20, Сеньор 50) при включённом овердрафте - 5 юнитов. Пул закончился - 1 поштучный кредит |
Строка пересчёта (POST /api/run/<id>/recalc/, как кнопка «Пересчитать» в кабинете) | 1 юнит или кредит за 2 строки; нечётная «половинка» переносится на следующий пересчёт. Пересчёт не тратит дневной лимит тарифа и не уходит в овердрафт |
Порядок работы
POST /api/run/estimate/- смета:charge.units,blocking[],can_launch.POST /api/run/с теми же полями +idempotency_key+confirm_costиз сметы.GET /api/run/<id>/pairs/раз в 1-2 минуты доfinished=true.GET /api/run/<id>/analysis/?ids=- что убрать; затемGET /api/run/<id>/tz/?ids=- что добавить.- Правка страницы.
POST /api/run/<id>/recalc/сdry_run=true- смета пересчёта.- Тот же запрос без
dry_run, сidempotency_keyиconfirm_cost→/pairs/нового прогона →GET /api/run/<новый id>/diff/. GET /api/usage/- журнал и остатки.
Смета: GET|POST /api/run/estimate/
Принимает те же поля, что запуск, ничего не создаёт и не списывает. Тело - JSON или form-data.
| Поле | Описание |
|---|---|
| pairs | Пары: [{"query":"...","url":"https://..."}] или строки «запрос;URL». Кириллические домены переводятся в punycode сами |
| mode | pairs (по умолчанию), niche (только запросы, поле queries) или html (запросы + общий HTML-шаблон: queries, site_html, project_name) |
| engine | yandex (по умолчанию) или google |
| region | id региона Яндекса числом: Москва 213 (по умолчанию), Санкт-Петербург 2 |
| top | Глубина ТОПа: 10, 20 (по умолчанию) или 30 |
| noindex, numeric, unions, normalizeDensity, excludeMyDomain, mode404 | Флаги анализа, как в форме кабинета |
| pageTypeFilter | all, main или inner - какие страницы конкурентов брать |
| stopWords, domainStopList | Свои стоп-слова и стоп-домены (список доменов заменяет список по умолчанию целиком) |
| skip_duplicates | true - пары, уже запущенные через API за 24 часа, пропустить, а остальные запустить |
| project_name | Проект в кабинете, куда положить прогон (создаётся, если его нет) |
Каждая строка проверяется той же формой, что при старте: ошибки приходят сразу (400, массив errors с номером строки), а не во время прогона.
| Поле ответа | Описание |
|---|---|
| rows_total / rows_to_launch | Сколько строк прислали и сколько уйдёт в работу после отсева повторов |
| duplicates[] | Пары, уже запущенные через API за 24 часа: existing_pair, existing_run, existing_status, launched_at, read - готовый адрес, где читать результат |
| charge | Цена в юнитах: units (всего), from_pool, from_credits, overdraft_rows, recalc_rows, pool_left, credits_left, daily_cap, spent_today (списано через API сегодня плюс ждёт старта), cap_left |
| confirm_cost | Число, которое нужно передать при запуске |
| limits | Лимиты на прогон, на сутки и на каждый сайт: сколько уже занято и сколько возьмёт этот прогон |
| eta_minutes | Грубая оценка длительности |
| blocking[] / can_launch | Что мешает запуску (code + message); can_launch=true - можно запускать |
Запуск: POST /api/run/
Те же поля, что у сметы, плюс два обязательных:
idempotency_key- ваша строка до 100 символов, напримерsite-remont-2026-10-08. Повтор запроса с тем же ключом (в течение 30 дней) не создаёт второй прогон, а возвращает уже созданный сreplayed=true. Сеть оборвалась - смело повторяйте тот же запрос.confirm_cost- число из сметы. Если цена к моменту запуска изменилась (например, часть пар стала повтором), прогон не создаётся:409с актуальной ценой. Возьмите её и повторите.
Ответ 201: run (id прогона), rows, skipped_duplicates, settings, status_endpoint, report_url, replayed.
curl -sL -X POST -H "Authorization: Bearer <ваш_токен>" -H "Content-Type: application/json" \
https://seolity.ru/api/run/ -d '{
"pairs": [{"query": "ремонт квартир под ключ", "url": "https://site.ru/remont/"}],
"engine": "yandex", "region": "213", "top": 20,
"idempotency_key": "site-remont-2026-10-08",
"confirm_cost": 1
}'
Пересчёт на тех же конкурентах: POST /api/run/<id>/recalc/
Создаёт новый прогон по готовым парам исходного, но выдачу заново не снимает: берёт те же адреса конкурентов, что были в исходном анализе. Поэтому разница «было/стало» показывает эффект правки вашей страницы, а не движение ТОПа.
| Поле | Описание |
|---|---|
| ids | public_id пар через запятую или массивом; без него - все готовые пары прогона |
| dry_run | true - только смета, без запуска |
| idempotency_key | Обязателен для запуска (не для сметы) |
| confirm_cost | Обязателен для запуска: число из сметы dry_run |
В смете: rows_to_recalc, charge и confirm_cost, not_ready (пары, которые ещё не досчитались), recently_recalced (эти пары уже пересчитывались через API за последний час и в пересчёт не войдут), fresh_serp_rows (у этих пар не сохранился список конкурентов, они пойдут по свежей выдаче), warnings, blocking[], can_launch.
Если исходному прогону 2 дня и больше, конкуренты те же (те же URL), но их тексты скачиваются заново: часть дельты может идти от правок у конкурентов. Об этом предупредит warnings. Прогон по HTML-шаблону (режим html) пересчитать нельзя - запустите новый с обновлённым шаблоном.
Ответ запуска - как у POST /api/run/, плюс source_run и diff_endpoint.
«Было/стало»: GET /api/run/<id>/diff/
<id> - прогон «стало». Для пересчёта прогон «было» определяется сам (исходный); для двух независимых прогонов передайте ?base=<id прогона «было»>. ?ids= - ограничить выборку парами «стало».
| Поле | Описание |
|---|---|
| same_competitors | true - «стало» посчитано пересчётом на тех же конкурентах; false - прогоны независимые, в дельту входит движение выдачи |
| days_between | Сколько дней между прогонами |
| summary | Сколько пар better / same / worse / unknown |
| pairs[].verdict | better / worse - показатель «Общая» изменился на 2 пункта и больше; same - меньше чем на 2 |
| pairs[].scores | Ширина, Глубина, Общая, BM25 и ранг BM25: before, after, delta |
| pairs[].text | Объём (myWords, topMedianWords, ratio, volumeAction), offTopicPercent, лишние повторы, число проблем глубины и рекомендаций высокого приоритета - тоже before / after / delta |
| pairs[].overspam | Переспам: fixed - ушёл, new - появился, still - остался (с числами до и после) |
| pairs[].width_missing | Недостающие слова: covered - добавлены, new - появились новые пробелы |
| pairs[].title, title_changed | Title до и после |
| skipped[] | Пары, которые не сравнились, с причиной: not_ready (не досчитаны), no_base_pair (нет пары в «было»), pending (отчёты ещё прогреваются - повторите запрос), error |
Динамику пары за всё время показывает GET /api/analysis/<id>/history/ (см. выше): точки с kind=recalc_same_competitors - чистый эффект правок.
Лимиты
| Лимит | Значение |
|---|---|
| Строк в одном запуске | 100 |
| Строк в сутки через API на аккаунт | 500 |
| Строк в сутки на один сайт (хост без www) | 100 |
| Та же пара (запрос + URL + поисковик + регион + ТОП + режим 404) через API | не чаще раза в 24 часа; повтор - 409 и duplicates[].read со ссылкой на готовый анализ (или skip_duplicates=true) |
| Пересчёт той же пары через API | не чаще раза в 60 минут |
| Дневной потолок списаний через API | задаёте сами на странице API; по умолчанию равен дневному лимиту тарифа (Джун 5, Мидл 20, Сеньор 50 юнитов) |
| Одновременно в парсинге | до 3 строк одного прогона; остальные ждут очереди. Анализы, запущенные в кабинете, этой очереди не ждут |
Суточные лимиты считаются по календарным суткам и обнуляются в полночь. Плюс общий предохранитель сервиса на все запуски через API за сутки.
Коды ответа
| Код | Что значит и что делать |
|---|---|
| 402 no_funds | Не хватает юнитов пула и кредитов. Пополнить: кошелёк |
| 429 | Дневной лимит исчерпан, заголовок Retry-After - секунд до полуночи. Код в ответе: spend_cap (ваш потолок дня, меняется на странице API), daily_limit (строк в сутки на аккаунт), site_daily_limit (строк в сутки на сайт), tariff_daily_limit (дневной лимит тарифа, а овердрафт выключен), global_daily_limit (общий предохранитель сервиса) |
| 409 | Запуск не создан: duplicates (повтор пары), confirm_cost не совпал со сметой, run_limit (больше 100 строк в запуске), empty (запускать нечего), unknown_ids (в пересчёте пары не из этого прогона), xmlstock_balance (сервису временно не хватает лимита на снятие выдачи) |
| 403 | Нет активной подписки |
| 400 | Ошибка во входных данных, список в errors |
На 409 и 429 агенту нужно остановиться и читать готовое, а не повторять тот же запрос.
Журнал: GET /api/usage/?days=30
Сколько списано через API и что осталось. Тот же журнал виден в кабинете на странице API, блок «Запуски через API».
| Поле | Описание |
|---|---|
| today | spent (списано сегодня), pending (ждёт старта), daily_cap, cap_left, pool_left, credits_left |
| runs[] | Прогоны за период: run, created_at, mode (анализ или пересчёт), rows, units, sources (сколько из пула, кредитов, овердрафта), key_tail (последние цифры ключа), report_url |
Порядок работы
- Запустите анализ (в кабинете или через
POST /api/run/после сметы) и дождитесьfinished=trueв/pairs/ - Начните с чистки:
summary.volume, переспам, слова не по теме, предложения-кандидаты на удаление - Посмотрите
recommendations- готовые действия с приоритетами - Посмотрите
widthMissing- добавьте недостающие тематические слова - Посмотрите
depthIssues- исправьте переспам и недобор - Используйте
ngrams- добавьте недостающие фразы - После правок пересчитайте анализ на тех же конкурентах (
POST /api/run/<id>/recalc/) и сравните показатели (GET /api/run/<новый id>/diff/)