Блог

Документация 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, bm25RankBM25-релевантность и ранг среди всех документов
queryCoverageПокрытие слов запроса: для каждого слова - сколько раз оно встречается

competitorsMedian

Медианы по конкурентам: countWords, hCount (заголовки), aCount (ссылки).

recommendations

Готовые рекомендации - начинать нужно с них:

ПолеОписание
severityhigh / medium / low - приоритет
typemissing, underused, overspam, query_coverage, length_short/length_long
wordКакое слово
nowСколько сейчас на странице
targetК чему стремиться (число или диапазон «X–Y»)
detailПояснение

widthMissing

Слова из корпуса конкурентов, которых нет на странице. Отсортированы по важности. Добавление увеличит показатель Width.

ПолеОписание
wordСлово (лемма)
medianМедиана вхождений у конкурентов
sitesНа скольких сайтах ТОП встречается

depthIssues

Слова, где частота далека от оптимальной. Исправление увеличит показатель Depth.

ПолеОписание
wordСлово
mineСколько раз на странице
medianНормированная медиана конкурентов (цель)
scoreDepth score 0..1 (чем ниже, тем хуже)
issuemissing, under, overspam
deltaРазница (median − mine). Положительный = добавить, отрицательный = убрать

words

Полная таблица слов (топ-200 по значимости):

ПолеОписание
wСлово (лемма, самая частая словоформа)
sНа скольких сайтах ТОП встречается
medМедиана по конкурентам
avgСреднее по конкурентам
mineСколько раз на странице (абсолютное число)
dДельта (med − mine)
medT / mineTТекст (body без анкоров): медиана / мой
medA / mineAАнкоры (текст в <a>): медиана / мой

ngrams

Устойчивые выражения (биграммы и триграммы):

ПолеОписание
phraseФраза
n2 (биграмма) или 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": "..." } ]
}

Масштаб: фоновая сборка

Для больших прогонов сборку запускают в фоне, затем забирают результат:

  1. POST /api/run/<id>/tz/build/ - запустить сборку всех ТЗ (?force=1 - пересобрать заново).
  2. GET /api/run/<id>/tz/status/ - прогресс (сколько готово из общего числа).
  3. GET /api/run/<id>/tz/ - листать готовые ТЗ (см. выше).
  4. 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Название потенциальной страницы
actionnew / 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_statusok - выдача есть; 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.

ПолеОписание
statuscreated / parsing (снимается выдача) / clustering (собираются кластеры) / done / error / pending_payment (ждёт оплаты)
queriestotal, 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
engineyandex (по умолчанию), 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_statuspending / running / done
finishedtrue, когда прогон завершён целиком
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 разные ТОПы, их точки в одну серию не смешиваются.

ПолеОписание
pairquery, url, html_mode, searchEngine, region, topN
points[]Точки серии: public_id, date, kind, metrics (width, depth, overall, bm25, countWords, textOnlyWords, anchorWords), is_requested (точка, по которой спросили)
points[].kindfresh_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.countNoindexTextfalse - текст в <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 строки; нечётная «половинка» переносится на следующий пересчёт. Пересчёт не тратит дневной лимит тарифа и не уходит в овердрафт

Порядок работы

  1. POST /api/run/estimate/ - смета: charge.units, blocking[], can_launch.
  2. POST /api/run/ с теми же полями + idempotency_key + confirm_cost из сметы.
  3. GET /api/run/<id>/pairs/ раз в 1-2 минуты до finished=true.
  4. GET /api/run/<id>/analysis/?ids= - что убрать; затем GET /api/run/<id>/tz/?ids= - что добавить.
  5. Правка страницы.
  6. POST /api/run/<id>/recalc/ с dry_run=true - смета пересчёта.
  7. Тот же запрос без dry_run, с idempotency_key и confirm_cost → /pairs/ нового прогона → GET /api/run/<новый id>/diff/.
  8. GET /api/usage/ - журнал и остатки.

Смета: GET|POST /api/run/estimate/

Принимает те же поля, что запуск, ничего не создаёт и не списывает. Тело - JSON или form-data.

ПолеОписание
pairsПары: [{"query":"...","url":"https://..."}] или строки «запрос;URL». Кириллические домены переводятся в punycode сами
modepairs (по умолчанию), niche (только запросы, поле queries) или html (запросы + общий HTML-шаблон: queries, site_html, project_name)
engineyandex (по умолчанию) или google
regionid региона Яндекса числом: Москва 213 (по умолчанию), Санкт-Петербург 2
topГлубина ТОПа: 10, 20 (по умолчанию) или 30
noindex, numeric, unions, normalizeDensity, excludeMyDomain, mode404Флаги анализа, как в форме кабинета
pageTypeFilterall, main или inner - какие страницы конкурентов брать
stopWords, domainStopListСвои стоп-слова и стоп-домены (список доменов заменяет список по умолчанию целиком)
skip_duplicatestrue - пары, уже запущенные через 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/

Создаёт новый прогон по готовым парам исходного, но выдачу заново не снимает: берёт те же адреса конкурентов, что были в исходном анализе. Поэтому разница «было/стало» показывает эффект правки вашей страницы, а не движение ТОПа.

ПолеОписание
idspublic_id пар через запятую или массивом; без него - все готовые пары прогона
dry_runtrue - только смета, без запуска
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_competitorstrue - «стало» посчитано пересчётом на тех же конкурентах; false - прогоны независимые, в дельту входит движение выдачи
days_betweenСколько дней между прогонами
summaryСколько пар better / same / worse / unknown
pairs[].verdictbetter / 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_changedTitle до и после
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».

ПолеОписание
todayspent (списано сегодня), pending (ждёт старта), daily_cap, cap_left, pool_left, credits_left
runs[]Прогоны за период: run, created_at, mode (анализ или пересчёт), rows, units, sources (сколько из пула, кредитов, овердрафта), key_tail (последние цифры ключа), report_url

Порядок работы

  1. Запустите анализ (в кабинете или через POST /api/run/ после сметы) и дождитесь finished=true в /pairs/
  2. Начните с чистки: summary.volume, переспам, слова не по теме, предложения-кандидаты на удаление
  3. Посмотрите recommendations - готовые действия с приоритетами
  4. Посмотрите widthMissing - добавьте недостающие тематические слова
  5. Посмотрите depthIssues - исправьте переспам и недобор
  6. Используйте ngrams - добавьте недостающие фразы
  7. После правок пересчитайте анализ на тех же конкурентах (POST /api/run/<id>/recalc/) и сравните показатели (GET /api/run/<новый id>/diff/)