# IndexNow и API Яндекс Вебмастера: автоматизируем индексацию

> Как после деплоя отправлять поисковикам только изменённые URL: IndexNow для Яндекса и Bing, переобход через API Вебмастера, Google без IndexNow. Код и проверка.
> Author: Roman Belov · Published: 2026-11-04 · Source: https://futurecraft.pro/ru/blog/indexnow-yandex-webmaster-api/

После деплоя статического сайта поисковики узнают о новой странице, когда робот снова обойдёт sitemap или найдёт на неё ссылку. Ускорить это можно двумя способами. IndexNow - открытый протокол, по которому сайт одним HTTP-запросом с ключом владельца сообщает поисковым системам о новых, изменённых и удалённых URL. Его поддерживают Bing, Яндекс, Naver, Seznam.cz и Yep; Google в протоколе не участвует, и индексацию IndexNow не гарантирует.

Второй способ работает только для Яндекса: переобход через API Яндекс Вебмастера. Он ставит конкретный URL в приоритетную очередь робота, но в пределах суточной квоты. У futurecraft.pro она 150 URL в день; у вашего сайта может быть другой, поэтому её читают через API, а не берут из статей.

Статья о том, как собрать из этого пайплайн после деплоя: что куда отправлять, скрипт, который берёт изменённые URL из git, шаг в GitHub Actions, переобход через API Вебмастера и проверка результата. Весь код взят из CI этого блога, включая разбор собственной ошибки: до ноября 2026 года CI отправлял в IndexNow не изменённые страницы, а первые 100 адресов из sitemap.

## Какие каналы существуют и что куда отправлять

Каналов шесть, и документированы не все. В таблице состояние на ноябрь 2026 года.

| Канал | Кто получает | Что передаёт | Лимит | Документирован |
|---|---|---|---|---|
| IndexNow (`api.indexnow.org` или endpoint поисковика) | Bing, Яндекс, Naver, Seznam.cz, Yep | новые, изменённые и удалённые URL | до 10 000 URL в запросе; суточные пороги у каждого поисковика свои | да |
| API Вебмастера: переобход страниц | Яндекс | один URL за запрос | суточная квота из `recrawl/quota` (у нас 150) | да |
| API Вебмастера: приоритетный переобход Sitemap | Яндекс | файл sitemap целиком | 10 раз в месяц | да, API v4.1 |
| Sitemap с `lastmod` | все поисковики | полный список URL | - | да |
| Обход по счётчику Метрики | Яндекс | страницы, которые посещают пользователи | - | да |
| PubSubHubbub (WebSub) | хаб `pubsubhubbub.appspot.com` | RSS- и Atom-ленты | - | хаб работает; влияние на поиск Google не описано |

Два механизма в таблицу не вошли. Пинг sitemap Google отключён, подробности в разделе про Google. Пинг Яндекса `webmaster.yandex.ru/ping?sitemap=...` отвечает HTTP 200, но ни в справке, ни в документации API его нет, и эффект ничем не подтверждён. Рекомендовать его нельзя, хотя наш CI его по-прежнему вызывает.

Рабочая схема для статического сайта: sitemap с точным `lastmod` как источник правды, IndexNow с изменёнными URL после каждого деплоя, переобход через API Вебмастера для новых статей. Яндекс в [справке о новых и изменённых страницах](https://yandex.ru/support/webmaster/ru/robot-workings/new-changed) сам советует совмещать способы.

## IndexNow: как устроен протокол

### Ключ и файл проверки

Ключ подтверждает, что запрос относится к вашему сайту. По [требованиям Яндекса](https://yandex.ru/support/webmaster/ru/indexnow/key) это строка от 8 до 128 символов из `a-z`, `A-Z`, `0-9` и `-`. Файл `<KEY>.txt` с ключом внутри, в кодировке UTF-8, кладут в корень сайта. Если файл лежит в другом каталоге, его адрес передают параметром `keyLocation`, и тогда ключ покрывает только URL из этого каталога.

```bash
KEY=$(openssl rand -hex 16)             # 32 символа: 0-9 и a-f
printf '%s' "$KEY" > "public/${KEY}.txt"  # Astro и Vite копируют public/ в корень сайта
# после деплоя: ожидаем 200 и text/plain
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' "https://example.com/${KEY}.txt"
```

Яндекс сверяет ключ при каждом запросе. Файл публичный по устройству протокола, так что ключ подтверждает связь с сайтом, а не аутентифицирует отправителя. Для смены ключа нужен новый файл и новый ключ в запросах.

### Запрос и коды ответа

Один URL отправляют GET-запросом, URL кодируют по RFC 3986:

```bash
curl -s -w '\nHTTP %{http_code}\n' \
  "https://api.indexnow.org/indexnow?url=https%3A%2F%2Fexample.com%2Fblog%2Fpost%2F&key=<KEY>"
```

Пакет уходит POST-запросом с JSON, до 10 000 URL за раз ([документация протокола](https://www.indexnow.org/documentation)):

```json
{
  "host": "example.com",
  "key": "<KEY>",
  "keyLocation": "https://example.com/<KEY>.txt",
  "urlList": [
    "https://example.com/blog/post/",
    "https://example.com/ru/blog/post/"
  ]
}
```

Заголовок запроса: `Content-Type: application/json; charset=utf-8`. Коды ответа по документации протокола и [справочнику Яндекса](https://yandex.ru/support/webmaster/ru/indexnow/reference):

| Код | Значение | Что делать |
|---|---|---|
| 200 | URL получены | ничего; это факт приёма, не индексации |
| 202 | приняты, ключ ещё проверяется | для нового ключа подождать; у Яндекса постоянный 202 значит, что файл ключа не найден |
| 400 | неверный формат запроса | проверить JSON и кодирование URL |
| 403 | ключ не найден или не совпадает с файлом | открыть `<KEY>.txt` по HTTPS |
| 405 | метод не поддерживается (у Яндекса) | использовать GET для одного URL или POST для пакета |
| 422 | URL не принадлежат `host`, ошибка в `keyLocation`, больше 10 000 URL | проверить хост и список |
| 429 | слишком много запросов | сократить частоту; у Яндекса лимит считается по IP |

Успех для скрипта - 200 и 202. Всё остальное должно выглядеть как ошибка, иначе отказ в приёме никто не заметит.

### Шлюзы и интервал повторной отправки

У каждого участника свой endpoint, плюс общий `https://api.indexnow.org/indexnow`. По [FAQ indexnow.org](https://www.indexnow.org/faq) отправка на любой из них передаётся всем участникам, а решение об индексации каждый поисковик принимает сам. Яндекс принимает запросы на `https://yandex.com/indexnow`, но в своей справке передачу другим участникам прямо не описывает.

С интервалами источники расходятся. indexnow.org советует ждать не меньше 5 минут перед повторной отправкой того же URL, а после ответа 429 - не меньше 10 минут. Яндекс рекомендует паузу 10 минут. Безопаснее ориентироваться на 10: при обычном деплое один и тот же URL всё равно не отправляется чаще.

## Что отправлять и когда

Отправлять нужно новые, изменённые и удалённые страницы, и ничего больше. На вопрос «Можно ли отправить URL всех страниц сайта?» [справка Яндекса](https://yandex.ru/support/webmaster/ru/indexing-options/index-now) отвечает: только новые, изменившиеся или удалённые, а для учёта всех изменений есть sitemap и обход по счётчику Метрики. [Bing](https://www.bing.com/indexnow/getstarted) добавляет, что каждый обход расходует квоту обхода сайта.

Удалённые страницы отправлять можно и полезно. URL с ответом 404 или 410 и адреса с редиректом 301 или 302 протокол принимает, так поисковик быстрее уберёт устаревшее из выдачи.

Для статического сайта «изменённая страница» вычисляется из git. Если в коммите поменялся файл статьи, изменилась её страница. Если поменялся шаблон, компонент или конфиг сборки, изменилась разметка всех страниц. Отправлять весь sitemap из-за правки футера - компромисс: indexnow.org просит не сообщать о косметических изменениях, а скрипт не умеет отличить косметику от смысла. В нашем скрипте выбран осторожный вариант: шаблон поменялся, значит, отправляем всё.

## Скрипт: отправка изменённых URL из git

`scripts/notify-indexnow.mjs` в репозитории блога работает без зависимостей, на Node.js 20. Он принимает `--base-ref=<git ref>` (с какого коммита считать изменения) и `--dry-run` (напечатать список, ничего не отправляя). Сначала скрипт берёт список изменённых файлов и превращает пути статей в URL:

```js
function changedFiles() {
  git(['rev-parse', '--verify', baseRef]);
  const output = git(['diff', '--name-only', `${baseRef}...HEAD`]);
  return output ? output.split('\n').filter(Boolean) : [];
}

function articleUrl(file) {
  const match = file.match(/^src\/content\/blog\/(en|ru)\/(.+)\.mdx?$/);
  if (!match) return null;
  const [, locale, slug] = match;
  const localePrefix = locale === 'ru' ? '/ru' : '';
  return `${siteOrigin}${localePrefix}/blog/${slug}/`;
}
```

`git()` здесь - обёртка над `execFileSync('git', args)`. Аргументы уходят массивом, без shell, а `baseRef`, начинающийся с `-` или содержащий пробелы, скрипт отклоняет до вызова git.

Дальше два правила расширяют список. Изменения в общих файлах добавляют все URL из собранного sitemap в `dist/`. Изменения данных витрины качества (страницы с метриками проектов, их данные CI обновляет автоматически) добавляют только страницы, которые эти данные рендерят:

```js
const dataPages = {
  'src/data/quality/': ['/engineering/', '/ru/engineering/', '/kronos/', '/ru/kronos/'],
};

function affectsEveryPage(file) {
  return [
    'astro.config.mjs', 'src/components/', 'src/content/config.',
    'src/data/categories.', 'src/i18n/', 'src/layouts/', 'src/pages/',
  ].some((prefix) => file === prefix || file.startsWith(prefix));
}
```

Перед отправкой каждый URL проходит проверку: только `https:` и только хост сайта. Ошибка в маппинге не отправит в поисковик чужой адрес. Ответ, отличный от 200 и 202, превращается в исключение с первыми 500 символами тела:

```js
const response = await fetch('https://api.indexnow.org/indexnow', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ host, key, keyLocation: `${siteOrigin}/${key}.txt`, urlList }),
});
if (![200, 202].includes(response.status)) {
  const body = (await response.text()).slice(0, 500);
  throw new Error(`IndexNow returned HTTP ${response.status}: ${body}`);
}
```

Так выглядит `--dry-run` на коммите, который публикует одну статью в двух локалях:

```text
$ node scripts/notify-indexnow.mjs --base-ref=<PREV_COMMIT> --dry-run
IndexNow: 2 changed URLs since <PREV_COMMIT>.
https://futurecraft.pro/blog/claude-code-russia/
https://futurecraft.pro/ru/blog/claude-code-russia/
```

На merge-коммите пакетного релиза из девяти статей тот же прогон вернул 18 URL. Картинки в `public/og/` и `llms.txt` тоже менялись в этом релизе, но страниц не порождают, и в список не попали.

У разностного подхода три особенности, которые легко упустить:

- **База должна быть прошлым продовым коммитом.** Если взять `origin/main` при деплое из ветки, список окажется неполным или пустым. В CI базу даёт `github.event.before`, при ручном деплое её задают переменной `INDEXNOW_BASE_REF`.
- **Переименование статьи теряло старый URL.** `git diff --name-only` по умолчанию распознаёт переименования и печатает только новый путь, поэтому адрес, который теперь отдаёт 404, в список не попадал. Скрипт вызывает diff с флагом `--no-renames`: в выводе оба пути, и поисковик узнаёт о старом адресе.
- **Удалённая статья даёт URL, который уже отдаёт 404.** Это ожидаемо: протокол принимает такие адреса.

## Шаг в CI: было и стало

До ноября 2026 года IndexNow в CI этого блога вызывался так (фрагмент, ключ заменён):

```yaml
- name: Notify search engines (IndexNow, Yandex, PubSubHubbub)
  continue-on-error: true
  run: |
    SITEMAP_URL="https://${SITE}/sitemap-index.xml"
    INDEXES=$(curl -s "$SITEMAP_URL" | grep -o '<loc>[^<]*</loc>' | sed 's/<\/*loc>//g')
    PAGES=""
    for sm in $INDEXES; do
      PAGES="${PAGES}$(curl -s "$sm" | grep -o '<loc>[^<]*</loc>' | sed 's/<\/*loc>//g')"$'\n'
    done
    URL_JSON=$(echo "$PAGES" | grep -v '^$' | head -100 | jq -R . | jq -s .)
    curl -s -X POST "https://api.indexnow.org/indexnow" \
      -H "Content-Type: application/json" \
      -d "{\"host\":\"${SITE}\",\"key\":\"<KEY>\",\"keyLocation\":\"https://${SITE}/<KEY>.txt\",\"urlList\":${URL_JSON}}" \
      -w "\nIndexNow: HTTP %{http_code}\n"
```

Шаг появился в июле 2026 года вместе с автодеплоем. Ручной `deploy.sh` ещё в августе перешёл на разностный скрипт, а CI-шаг обновить забыли, и он проработал так почти четыре месяца. Ошибок в нём три, и все видны при чтении кода.

1. **`head -100` берёт первые 100 адресов sitemap, а не изменённые.** На 6 ноября 2026 года в sitemap блога 336 URL. Они идут по алфавиту пути, и первая русская страница стоит на 168-м месте. Шаг не отправил ни одного из 168 русских адресов, а для Яндекса русские страницы главные. Новая английская статья попадала в отправку, только если её slug начинался с букв от `a` до примерно `j`.
2. **Неизменённые страницы уходили при каждом деплое.** Это прямо противоречит рекомендациям протокола и Яндекса и тратит квоту обхода.
3. **Ошибка ответа была не видна.** `curl` печатал код, шаг всегда завершался успешно. Ответ 403 из-за недоступного файла ключа или 429 можно было обнаружить, только перечитывая логи.

Исправленный шаг вызывает разностный скрипт. Базой служит не прошлый пуш, а коммит последнего успешного деплоя: его возвращает API GitHub Actions.

```yaml
permissions:
  contents: read
  actions: read   # чтобы узнать последний успешный деплой
# ...
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # нужна история, чтобы сравнить с продовым коммитом

- name: Notify IndexNow (changed URLs only)
  continue-on-error: true
  env:
    GH_TOKEN: ${{ github.token }}
    INDEXNOW_KEY: <KEY>
    PUSH_BEFORE: ${{ github.event.before }}
  run: |
    BASE=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?branch=main&status=success&per_page=1" \
      --jq '.workflow_runs[0].head_sha // empty' || true)
    git cat-file -e "${BASE}^{commit}" 2>/dev/null || BASE="$PUSH_BEFORE"
    git cat-file -e "${BASE}^{commit}" 2>/dev/null || BASE="$(git rev-parse HEAD~1)"
    node scripts/notify-indexnow.mjs --base-ref="$BASE"
```

Почему не `github.event.before`. Это SHA ветки `main` до пуша, и в первой версии исправления базой был он. Но workflow использует `concurrency` с `cancel-in-progress: true`: если новый пуш отменит деплой предыдущего, следующий запуск посчитал бы разность от отменённого коммита, и его URL в IndexNow не ушли бы. При ручном запуске (`workflow_dispatch`) и вызове из другого workflow (`workflow_call`) `before` нет вовсе. Последний успешный деплой закрывает все три случая, а `before` и `HEAD~1` остались запасными вариантами. Если workflow вызывается из другого, право `actions: read` нужно выдать и вызывающему: вложенный workflow не получит больше прав, чем у него. По умолчанию `actions/checkout` скачивает один коммит, отсюда `fetch-depth: 0`.

`continue-on-error: true` осталось, но смысл поменялся. Раньше шаг не мог упасть. Теперь скрипт бросает исключение на любом ответе, кроме 200 и 202: шаг отмечается ошибкой, а деплой считается успешным. Сайт уже выложен, и откатывать его из-за отказа поисковика не нужно. Пинг Яндекса и PubSubHubbub вынесены в отдельный шаг, чтобы их результат не смешивался с IndexNow.

До мержа исправление проверено прогоном `--dry-run` на диффе одной статьи: ровно 2 URL, английский и русский. Первый боевой запуск пришёлся на пачку из четырёх мержей подряд. Три промежуточных деплоя отменились, последний прошёл, и шаг взял базой последний успешный деплой до пачки:

```
Base: last successful deploy (2a9fee1…).
IndexNow: 344 changed URLs since 2a9fee1….
IndexNow: HTTP 200
```

344 адреса здесь не ошибка. В пачке был PR, который менял файлы в `src/pages/`, а такие изменения затрагивают все страницы, и скрипт отправляет весь sitemap. В том числе 168 русских URL, которые старый шаг с `head -100` не отправлял ни разу. Обычный деплой одной статьи даёт 2 адреса.

Если CI в этой части нужен с нуля, YAML для GitHub Actions удобно собирать по [промптам генератора CI/CD](https://futurecraft.pro/ru/blog/cicd-pipeline-generator/).

## API Вебмастера: переобход приоритетных URL

IndexNow сообщает об изменении всем участникам сразу. Переобход через API Вебмастера ставит конкретный URL в приоритетную очередь робота Яндекса, и по каждому URL можно отследить статус. Обратная сторона: нужен OAuth-токен, а число URL ограничено суточной квотой.

### OAuth-токен и права

По [инструкции Яндекса](https://yandex.ru/dev/webmaster/doc/ru/tasks/how-to-get-oauth) приложение регистрируют на `oauth.yandex.ru/client/new`: платформа «Веб-сервисы», redirect URI `https://oauth.yandex.ru/verification_code`, права `webmaster:hostinfo` и `webmaster:verify`. Токен получают по ссылке `https://oauth.yandex.ru/authorize?response_type=token&client_id=<CLIENT_ID>`.

Токен действует 6 месяцев, потом его нужно получить заново по той же ссылке. Запросы без токена получают 401. В примерах справочника API токен передаётся заголовком `Authorization: OAuth <TOKEN>`. Хранить его нужно в секретах CI или в локальном файле окружения вне репозитория, но не в коде.

### user-id и host-id

Все методы переобхода требуют два идентификатора. Их получают один раз:

```bash
AUTH="Authorization: OAuth ${YWM_TOKEN}"
curl -s -H "$AUTH" https://api.webmaster.yandex.net/v4/user | jq .user_id
curl -s -H "$AUTH" "https://api.webmaster.yandex.net/v4/user/${YWM_USER_ID}/hosts" \
  | jq -r '.hosts[] | "\(.host_id)  \(.unicode_host_url)  verified=\(.verified)"'
```

`host_id` имеет вид `https:example.com:443`. Переобход работает только для подтверждённого сайта, иначе API вернёт 404 `HOST_NOT_VERIFIED`.

### Отправка и квота

[Метод переобхода](https://yandex.ru/dev/webmaster/doc/ru/reference/host-recrawl-post): `POST /v4/user/{user-id}/hosts/{host-id}/recrawl/queue` с телом `{"url": "..."}`. В ответ приходит 202 с `task_id` и `quota_remainder`, остатком суточной квоты. Ошибки: 400 `INVALID_URL`, 403 `INVALID_USER_ID`, 404 `HOST_NOT_VERIFIED`, 409 `URL_ALREADY_ADDED` и 429 `QUOTA_EXCEEDED` (в теле `daily_quota` и `exceeded_until`, время сброса квоты).

Числа квоты в документации нет, в примерах ответов демо-значения. [Метод `recrawl/quota`](https://yandex.ru/dev/webmaster/doc/ru/reference/host-recrawl-quota-get) возвращает реальное значение для конкретного сайта. Для futurecraft.pro 6 ноября 2026 года ответ был таким:

```json
{ "daily_quota": 150, "quota_remainder": 150 }
```

Это наблюдение для одного хоста, а не гарантия: у другого сайта квота может быть другой. Скрипт ниже сначала читает остаток и отправляет URL, пока он не кончится. Передавать URL лучше в порядке важности: новые статьи, затем обновлённые, затем остальное.

```bash
#!/usr/bin/env bash
# recrawl.sh URL...: отправить URL на переобход в пределах суточной квоты
set -uo pipefail
API="https://api.webmaster.yandex.net/v4/user/${YWM_USER_ID}/hosts/${YWM_HOST_ID}"
AUTH="Authorization: OAuth ${YWM_TOKEN}"

left=$(curl -sf --max-time 20 --retry 2 -H "$AUTH" "$API/recrawl/quota" \
  | jq -er .quota_remainder) || { echo "Квоту не получить (сеть или токен), пропускаю"; exit 0; }

for url in "$@"; do
  [ "$left" -gt 0 ] || { echo "Квота на сегодня исчерпана"; break; }
  code=$(curl -s -o resp.json -w '%{http_code}' --max-time 20 -X POST \
    -H "$AUTH" -H "Content-Type: application/json" \
    -d "$(jq -nc --arg u "$url" '{url: $u}')" "$API/recrawl/queue")
  case "$code" in
    202) left=$(jq -r .quota_remainder resp.json)
         echo "OK $url task=$(jq -r .task_id resp.json) left=$left" ;;
    409) echo "Уже в очереди: $url" ;;
    429) echo "Квота исчерпана: $(cat resp.json)"; break ;;
    *)   echo "HTTP $code для $url: $(cat resp.json)" ;;
  esac
done
```

Скрипт написан по документации и проверен `shellcheck`; ветка недоступного API (неверный токен, выход с кодом 0) проверена прогоном. Запросы на отправку при подготовке статьи не выполнялись, чтобы не тратить квоту. Сам метод `recrawl/queue` проверен раньше: с тем же заголовком он вернул 202 и `task_id`.

Есть отдельный механизм для крупных изменений: [приоритетный переобход Sitemap](https://yandex.ru/dev/webmaster/doc/ru/reference/host-priority-re-crawl-sitemap) в API v4.1. Робот загружает файл sitemap сразу, не дожидаясь планового обхода. Лимит: 10 раз в месяц, остаток показывает `GET /v4.1/user/{user-id}/hosts/{host-id}/sitemaps/recrawl`. Подходит для релиза, после которого поменялись десятки страниц, но в ежедневный деплой его встраивать не стоит.

### Статус задач

`GET .../recrawl/queue/{task-id}` возвращает `state`, у которого три значения:

- `IN_PROGRESS` - задача в очереди;
- `DONE` - робот загрузил URL;
- `FAILED` - загрузить не удалось; проверьте доступность страницы и скорость ответа.

`DONE` не означает, что страница уже в поиске. Справка Яндекса называет для переобхода срок индексирования от 3 до 7 дней. Список всех задач отдаёт `GET .../recrawl/queue`: у futurecraft.pro на 6 ноября 2026 года в нём 79 задач, и у всех, что вернул API, статус `DONE`.

Путь эндпоинта лучше сверять с документацией, а не с памятью. MCP-клиент, через который мы работали с Вебмастером, обращался к `/recrawl/tasks` и получал «Resource not found». Рабочий путь - `/recrawl/queue`, он и указан в текущей документации.

## Google: что остаётся без IndexNow

Google в IndexNow не участвует: ни на главной indexnow.org, ни в FAQ протокола его нет. Остальные способы ускорить обнаружение страниц для Google тоже сузились:

- **Пинг sitemap отключён.** Google [объявил об этом в июне 2023 года](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) с отсрочкой в 6 месяцев; запросы на старый endpoint возвращают 404. Вместо него Google просит корректный `lastmod` в sitemap, ссылку на sitemap в robots.txt и отправку через Search Console.
- **Indexing API не для блогов.** По [документации](https://developers.google.com/search/apis/indexing-api/v3/quickstart) он предназначен только для страниц с разметкой `JobPosting` или `BroadcastEvent` внутри `VideoObject`. Скрипт для Indexing API в этом блоге был, в августе 2026 года его удалили.
- **Запрос индексирования остался ручным.** «Проверка URL» в Search Console позволяет запросить индексирование страницы, но только в интерфейсе и в пределах квоты. URL Inspection API статус только читает.
- **PubSubHubbub работает как хаб.** CI блога отправляет `hub.mode=publish` для RSS-лент на `pubsubhubbub.appspot.com`. Хаб отвечает, но документации Search Central о том, что Google Поиск использует WebSub для индексации обычных страниц, найти не удалось. Влияние на индексацию не измерено.

Для Google основная работа - точный `lastmod`, внутренние ссылки на новые страницы и sitemap в Search Console. IndexNow на Google не влияет.

## Как проверить результат

Ответ 200 от IndexNow означает только «URL получены». Проверять нужно на стороне поисковика, и у каждого канала свой способ:

| Канал | Где проверить | Что означает успех |
|---|---|---|
| IndexNow → Bing | Bing Webmaster Tools, отчёт по URL, полученным через IndexNow | URL видны как полученные |
| IndexNow → Яндекс | Яндекс Вебмастер (по справке, результаты IndexNow видны там) | страница загружена роботом |
| Переобход Яндекса | `GET recrawl/queue/{task-id}`, страница «Переобход страниц» | `DONE` |
| Индекс Яндекса | Вебмастер: страницы в поиске, история индексирования | URL в поиске |
| Google | Search Console: «Проверка URL», отчёт «Страницы» | «URL есть в индексе Google» |

Пример из практики: в конце сентября 2026 года после SEO-релиза в IndexNow ушло 127 URL, и Bing Webmaster Tools показал их полученными примерно через 13 часов. Это время до подтверждения приёма, а не до индексации: скорость попадания в индекс мы не измеряли.

Перед любой отправкой проверьте, что HTML на продакшене уже обновился. Поисковик получит сигнал, придёт за страницей и увидит то, что отдаст CDN:

```bash
curl -sI https://example.com/ru/blog/post/ | grep -i -E '^(age|cache-control|cf-cache-status|last-modified):'
```

Большой `age` при `cf-cache-status: HIT` означает, что CDN отдаёт старую копию.

## Эксплуатационные нюансы из практики

**API Вебмастера бывает недоступен.** 11 октября 2026 года не работал весь сервис: интерфейс показывал «Сервис временно недоступен», API не отвечал. С 11 по 22 октября `api.webmaster.yandex.net` не отвечал с рабочей машины по таймауту соединения; похоже на сетевую блокировку, а не на проблему с токеном. 6 ноября вызовы снова проходят. Отсюда правило: шаг переобхода не должен ломать деплой, нужны таймауты, пара повторов и лог. При долгой недоступности источник правды - интерфейс Вебмастера в браузере.

**CDN отдаёт старый HTML.** 22 октября 2026 года одна из точек присутствия Cloudflare отдавала уже снятую страницу с возрастом около 27 часов: зона кэшировала HTML с `s-maxage=604800`, то есть на неделю. Уведомление поисковика не гарантирует, что робот получит свежую версию. После деплоя нужно проверить заголовки и при необходимости сбросить кэш; для этого у токена CI должны быть права на purge.

**CI может не стартовать.** В октябре 2026 года workflow этого блога завершался с `startup_failure`: истекла карта для оплаты GitHub. Релиз выкатили вручную через `deploy.sh`, а IndexNow и PubSubHubbub отправили руками. Уведомление не должно жить только в CI. В блоге `deploy.sh` вызывает тот же `notify-indexnow.mjs` с базой из `INDEXNOW_BASE_REF` и отказывается деплоить что-либо, кроме чистого `origin/main`.

**Токен истекает через 6 месяцев.** Дату выпуска удобно записать рядом с секретом и поставить напоминание. Признак истёкшего токена - 401 от API, его полезно выводить отдельно от сетевых ошибок. В скрипте выше обе ситуации пока сведены в одно сообщение.

**Не все окна данных длинные.** Часть отчётов Вебмастера в API отдаёт ограниченное окно: мониторинг поисковых запросов, например, показывает данные только за последние две недели. Для проверки результата надёжнее статусы задач переобхода и история индексирования.

## Смежные API Яндекса: Wordstat и Метрика

Эти API не про индексацию, но в SEO-пайплайне обычно живут рядом.

**Wordstat API** теперь входит в Yandex Search API в AI Studio ([документация](https://aistudio.yandex.ru/docs/ru/search-api/concepts/wordstat)). Методы: `GetTop` (популярные запросы со словом за 30 дней), `GetDynamics` (динамика по месяцам, неделям или дням), `GetRegionsDistribution` и `GetRegionsTree`. Работает синхронно, для запросов нужна роль `search-api.webSearch.user`, квоты и тарифы описаны в отдельных разделах документации. Как использовать данные о запросах при планировании контента, разобрано в статье про [исследование ключевых слов с AI](https://futurecraft.pro/ru/blog/keyword-research-ai/).

**API отчётов Метрики** ([справка](https://yandex.ru/support/metrica/ru/uploading-data/api-reports)) отдаёт агрегированную статистику по группировкам и метрикам в JSON или CSV. Для индексации полезна косвенно: показывает, пошёл ли поисковый трафик на новую страницу.

`llms.txt` и Markdown-версии страниц тоже помогают машинам находить контент, но поисковиков об изменениях не уведомляют. Это отдельная тема, она разобрана в [гайде по llms.txt](https://futurecraft.pro/ru/blog/llms-txt-guide/). Как сигналы индексации соотносятся с цитированием в AI-поиске Bing, показано в [кейсе про 3 500 цитирований в Copilot](https://futurecraft.pro/ru/blog/geo-copilot-case/).

## Чек-лист автоматизации

1. Сгенерировать ключ IndexNow (8-128 символов), положить `<KEY>.txt` в корень сайта и проверить, что файл отдаётся с кодом 200.
2. Получать список изменённых URL из `git diff` между прошлым и новым продовым коммитом, а не из sitemap.
3. Сопоставить пути контента с URL всех локалей; изменения шаблонов и конфигов сборки разворачивать во весь sitemap.
4. Пропускать в отправку только URL своего хоста по HTTPS.
5. Считать успехом 200 и 202, остальные коды показывать ошибкой шага, не ломая деплой.
6. В GitHub Actions задать `fetch-depth: 0` и базой брать коммит последнего успешного деплоя (запасные варианты: `github.event.before`, затем `HEAD~1`).
7. Новые страницы дополнительно ставить на переобход через API Вебмастера, предварительно прочитав `recrawl/quota`.
8. Хранить OAuth-токен в секретах, записать дату его выпуска и обновлять раз в 6 месяцев.
9. Перед отправкой проверять, что CDN отдаёт новый HTML.
10. Через сутки проверить приём в Bing Webmaster Tools и статусы задач переобхода, через неделю - страницы в поиске.

Если публикация статьи у вас уже автоматизирована, шаг уведомления встаёт в конец пайплайна, после деплоя; пример такого пайплайна разобран в статье про [контент-движок на AI](https://futurecraft.pro/ru/blog/ai-content-engine/).

## Источники

- IndexNow: [документация протокола](https://www.indexnow.org/documentation), [FAQ](https://www.indexnow.org/faq)
- Bing: [IndexNow, начало работы](https://www.bing.com/indexnow/getstarted)
- Яндекс, справка Вебмастера: [поддержка IndexNow](https://yandex.ru/support/webmaster/ru/indexing-options/index-now), [справочник запросов IndexNow](https://yandex.ru/support/webmaster/ru/indexnow/reference), [ключ IndexNow](https://yandex.ru/support/webmaster/ru/indexnow/key), [новые и изменённые страницы](https://yandex.ru/support/webmaster/ru/robot-workings/new-changed)
- API Яндекс Вебмастера: [авторизация](https://yandex.ru/dev/webmaster/doc/ru/tasks/how-to-get-oauth), [отправка на переобход](https://yandex.ru/dev/webmaster/doc/ru/reference/host-recrawl-post), [квота](https://yandex.ru/dev/webmaster/doc/ru/reference/host-recrawl-quota-get), [статус задачи](https://yandex.ru/dev/webmaster/doc/ru/reference/host-recrawl-task-get), [приоритетный переобход Sitemap](https://yandex.ru/dev/webmaster/doc/ru/reference/host-priority-re-crawl-sitemap)
- Google: [прекращение пинга sitemap](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping), [Indexing API](https://developers.google.com/search/apis/indexing-api/v3/quickstart)
- Yandex AI Studio: [Wordstat API](https://aistudio.yandex.ru/docs/ru/search-api/concepts/wordstat); Метрика: [API отчётов](https://yandex.ru/support/metrica/ru/uploading-data/api-reports)
