Справочник: SEO
Что здесь. Все ручки, которыми читается и меняется SEO сайта, разложенные так же, как чеклист раздела SEO в дашборде: «Основа», «Индексация» (со счётчиками и консолями), «Контент», «Schema-разметка», «Скорость», «Локализация»; за ними — платные инструменты раздела: «Позиции в выдаче» и «Отправка на индексацию». Своего хранилища у чеклиста нет: всё, что он показывает, лежит в настройках сайта, Yoast, теме и страницах и правится там же. Каждая ручка, параметр и код отказа на этой странице проверены вызовом.
Как пользоваться. Сначала проверка целиком — она отдаёт все пункты со статусом и готовой командой исправления. Потом находите группу пункта: в таблице группы — ручка чтения и правки и пример тела, ниже, под заголовком пункта, — что ручка принимает, что отвечает и чем отказывает.
Адреса. Ручки v2 — https://onpress.pro/api/v2/site/{siteId}/…, права — администратор сайта или суперадмин сети. Несколько пунктов пока правятся ручками первой версии на домене самого сайта — https://{домен}/wp-json/onpress/v1/…, с тем же токеном; отказ там приходит в форме WordPress REST (code вместо error). В примерах op_XXXX — ваш токен, SITE_ID — внешний идентификатор сайта (поле site_id ответа GET /sites/{id}), example.com — домен сайта. Как устроено SEO платформы — SEO.
Проверка целиком
| Пункт | Ручка | Тело |
|---|---|---|
| Проверить сейчас | POST /site/{siteId}/seo/audit | — |
| Последний итог | GET /site/{siteId}/seo/audit | — |
Проверить сейчас
Считает весь чеклист на сервере: настройки Yoast и сайта, тему, языки и главные, набор author, счётчики и панели, обход страниц — и сохраняет итог: его отдаёт GET без новой проверки. Длится столько же, сколько обход страниц: от секунд до минуты. Второй POST, пока идёт первый, получает его результат. Источник, который не ответил, переводит свои пункты в unknown и называется в errors; остальные считаются.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/seo/audit" -H "Authorization: Bearer op_XXXX"
{
"success": true,
"site_id": "SITE_ID",
"checked": true,
"format": 1,
"checked_at": "2026-09-29T09:25:16.998Z",
"took_ms": 1202,
"site_url": "https://example.com",
"totals": { "counted": 38, "ok": 19, "todo": 16, "warn": 3, "na": 1, "unknown": 0 },
"sitemap": { "url": "https://example.com/sitemap_index.xml", "consoles": { "gsc": { "error": "not_connected" }, "ywm": { "error": "not_connected" } }, "error": null },
"errors": [],
"checks": [
{
"id": "identity",
"group": "Basics",
"status": "ok",
"title": { "key": "seo.checks.identity.title" },
"why": { "key": "seo.checks.identity.why" },
"summary": "Example",
"fields": [ { "label": { "key": "seo.checks.identity.fields.title" }, "value": "Example" } ],
"fix": { "title": "PATCH /api/v2/site/settings", "command": "curl -X PATCH …" }
}
]
}
Пункт: id, group (Basics, Markup, Indexing, Search consoles, Contacts, Speed, Pages — в дашборде «Контент»), status — ok, todo (сделать), warn (требует внимания), na (неприменимо), unknown (источник не ответил); summary, fields, note; fix — готовая команда curl с этим сайтом; builtin: true — то, что платформа делает сама. na и unknown в totals.counted не входят. Тексты — не слова, а ключи словаря дашборда: {"key", "params"}, {"list": […]}, {"join": […], "sep"}, {"age": iso}; простая строка — значение как есть. У пунктов homes и contacts вместо полей — таблица по языкам, у keywords — таблица страниц с ключом (см. ниже).
Последний итог
Отдаёт сохранённый итог без новой проверки — раздел SEO дашборда при открытии делает ровно этот вызов. До первой проверки: checked: false, checked_at: null, checks: []. Только в GET приходит access — переключатель «открыт / закрыт» в форме GET /site/{siteId}/access, прочитанный вживую (null, если прочитать не удалось).
curl -s "https://onpress.pro/api/v2/site/SITE_ID/seo/audit" -H "Authorization: Bearer op_XXXX"
Основа
| Пункт | Ручка | Тело |
|---|---|---|
| Сайт открыт / закрыт | GET /site/{siteId}/data/settings/closed PUT /site/{siteId}/data/settings/closed | {"value": true} |
| Название сайта | GET /site/{siteId}/data/settings/name PUT /site/{siteId}/data/settings/name | {"value": "Example"} |
| Favicon | GET /site/{siteId}/data/settings/icon PUT /site/{siteId}/data/settings/icon | {"value": 4827} |
Поля «Основы» — тонкий слой над ручками настроек, а не своё хранилище: запись названия и иконки уходит в PATCH /site/{siteId}/settings (blogname, site_icon), режим «закрыт» — в PUT /site/{siteId}/access ({"closed": …}), главная — в PUT /site/{siteId}/front-page ({"page", "lang"}). Ими можно пользоваться и напрямую — пишут они в те же опции.
Общее для полей /data/settings/{field}: запись — PUT с телом {"value": …}, ответ — поле после записи плюс changed (поменялось ли что-то), cache, forwards_to и rebaked (фреймы, которые показывают название сайта или главную, пересобираются, кэш зависимых страниц сбрасывается; при changed: false — rebaked.status: "nothing"). В имени поля можно писать дефис: front-page. Отказы слоя: 404 unknown_settings_field (незнакомое поле, fields — список), 400 value_required (в теле нет value), 405 wrong_method_for_settings (метод, кроме GET и PUT).
Сайт открыт / закрыт
Технический режим обслуживания. value: true — закрыть, false — открыть; "1", "0", "true", "false" тоже принимаются, другое — 400 invalid_closed. Закрытый сайт отвечает посетителям 503 с заглушкой «сайт на обслуживании» (название сайта, язык по адресу), Retry-After: 3600 и X-Robots-Tag: noindex; robots.txt отдаёт User-agent: * / Disallow: / без Sitemap:, карты сайта отвечают 503, llms.txt — 404; статические копии страниц снимаются, кэш сбрасывается. Свой текст robots.txt при этом сохраняется и возвращается при открытии. Видят сайт как обычно администратор сайта с входом в WordPress, REST (/wp-json/), вход и внутренние запросы платформы. Открытие возвращает статические копии и сбрасывает кэш.
При чтении рядом со значением — supported, reason, retry_after. Закрыть можно только сайт v2: на первой версии supported: false, reason: "not_v2", запись — 409 not_supported; главный сайт сети не закрывается (reason: "main_site", запись — 409 main_site).
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/data/settings/closed" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"value": true}'
{
"success": true,
"site_id": "SITE_ID",
"field": "closed",
"value": true,
"supported": true,
"reason": null,
"retry_after": 3600,
"changed": true,
"cache": { "…": "…" },
"forwards_to": "PUT /site/access {closed}",
"rebaked": { "status": "ok", "…": "…" }
}
Название сайта
value — непустая строка, пробелы по краям срезаются; пустая строка или не строка — 400 invalid_name. Название идёт в заголовки страниц («Страница – Название»), в разметку WebSite и Organization и во фреймы, которые его показывают.
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/data/settings/name" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"value": "Example"}'
{
"success": true,
"site_id": "SITE_ID",
"field": "name",
"value": "Example",
"changed": true,
"cache": { "object": true, "supercache": { "status": "empty" }, "polylang": { "…": "…" } },
"forwards_to": "PATCH /site/settings {blogname}",
"rebaked": { "status": "ok", "total": 8, "baked": 11, "frames": ["…"] }
}
Favicon
value — id картинки из медиатеки этого сайта; 0 или null снимает иконку. Не целое число — 400 invalid_icon; вложения с таким id нет или это не картинка — 400 invalid_site_icon. При чтении рядом — url. Иконка — это и favicon, и логотип организации и издателя в разметке: без неё издатель уходит без логотипа. Id картинки даёт загрузка: POST /site/{siteId}/media — поле id ответа, или поиск в GET /site/{siteId}/media.
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/data/settings/icon" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"value": 4827}'
Дополнительно: все поля разом
GET /site/{siteId}/data/settings — settings (поле → значение), details (поле → что прилагается к значению) и fields (поле → как оно пишется). Записи всех полей разом нет: PUT /site/{siteId}/data/settings — 405 wrong_method_for_settings.
curl -s "https://onpress.pro/api/v2/site/SITE_ID/data/settings" -H "Authorization: Bearer op_XXXX"
{
"success": true,
"site_id": "SITE_ID",
"settings": {
"name": "Example",
"icon": 4827,
"closed": false,
"front_page": 5776,
"languages": [
{ "slug": "en", "locale": "en_US", "name": "English", "default": true, "front_page": 5776, "url": "https://example.com/" },
{ "slug": "ru", "locale": "ru_RU", "name": "Русский", "default": false, "front_page": 6037, "url": "https://example.com/ru/" }
]
},
"details": {
"icon": { "url": "https://example.com/wp-content/uploads/sites/233/2025/02/icon.png" },
"closed": { "supported": true, "reason": null, "retry_after": 3600 },
"front_page": { "show_on_front": "page", "title": "home", "url": "/", "languages": { "en": 5776, "ru": 6037 } },
"languages": { "default": "en" }
},
"fields": { "name": { "method": "PUT", "at": "/data/settings/name", "body": "{\"value\": …}", "forwards_to": "PATCH /site/settings {blogname}" }, "…": "…" }
}
Индексация
На сайте второй версии публичны только страницы. Архивов авторов, дат, вложений, рубрик и меток, их лент и типа записей post нет (он остаётся только на сайте, где уже есть опубликованные записи) — такие адреса отвечают 404, в карту сайта не попадают, поэтому и пунктов про их индексацию в чеклисте нет. Стандартный REST WordPress (/wp-json/wp/v2/…, yoast/v1, oembed/1.0 и другие) снаружи без входа закрыт, кроме списка страниц GET /wp-json/wp/v2/pages и языков GET /wp-json/pll/v1/languages; работа с сайтом — через ручки /api/v2/ и /wp-json/onpress/v1/.
| Пункт | Ручка | Тело |
|---|---|---|
| Техническая база | GET /site/{siteId}/seo/audit → tech-basics GET /site/{siteId}/seo → sitemap GET /site/{siteId}/seo/pages → signals | встроено |
| Карта сайта в Google и Яндексе | GET /site/{siteId}/sitemap POST /site/{siteId}/sitemap/submit | {"service": "all"} |
| Адреса в карте сайта | GET /site/{siteId}/sitemap/urls | — |
| robots.txt | GET /site/{siteId}/robots PUT /site/{siteId}/robots DELETE /site/{siteId}/robots | {"content": "User-agent: *\nDisallow: /private/\n"} |
| llms.txt | GET /site/{siteId}/llms PUT /site/{siteId}/llms POST /site/{siteId}/llms/build | {"content": "…"} |
| Счётчики | GET /site/{siteId}/seo/audit → countersGET /wp-json/onpress/v1/analytics/statusGET /site/{siteId}/settings → analytics | подключаются в «Аналитика → Счётчики» |
Техническая база
Это платформа делает сама — настраивать ничего не нужно. В итоге проверки (POST /site/{siteId}/seo/audit) пункт tech-basics несёт rows: по строке на каждую вещь, status ok или warn (на сайте сломано — весь пункт не ok). У строки карты — url индекса, count адресов (сумма <url> по дочерним картам живого индекса) и bots — когда боты последний раз забирали карту: визит к индексу или к любой его дочерней карте, по каждому боту самый свежий.
Карта сайта XML. Итог пункта — «включена» и число адресов в картах страниц (page-sitemap*.xml из индексного файла Yoast), поля — индексный файл и «Страниц в карте». Карта не прочиталась — «включена» без числа; включена, но пустая — предупреждение; выключена — «сделать» с командой ниже. Раздел sitemap ручки PATCH /site/{siteId}/seo: enabled, author (bool; на второй версии не действует — архивов авторов нет), entries_per_page — от 1 до 10 000 (иначе 400 invalid_entries_per_page). При чтении — ещё index_url и governed_by. Состав карты отдельно не правится: sitemap.post_types в записи даёт 400 sitemap_composition_readonly; на сайте второй версии в карте только страницы.
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/seo" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"sitemap": {"enabled": true}}'
Canonical ведёт на саму страницу. У каждой страницы canonical на саму себя. Подтверждение — signals.canonical обхода: with, self, other и missing со списками страниц.
301 после переноса страницы. Старые адреса перенесённых страниц отвечают 301 на новые. Подтверждение — signals.redirects: сколько страниц отвечают редиректом и куда.
Честная 404 с noindex. Несуществующий адрес отвечает 404 с noindex. Подтверждение — signals.not_found: сколько таких ответов и сколько из них закрыты от индексации.
Карта сайта в Google и Яндексе
GET /site/{siteId}/sitemap — что Search Console и Вебмастер знают о карте сайта (sitemap_index.xml сайта). POST /site/{siteId}/sitemap/submit — отправить карту: тело {"service": "gsc" | "ywm" | "all", "sitemap_url": "https://…"}, оба поля необязательны (по умолчанию all и карта сайта). Отправляет сам сайт штатными методами подключённой аналитики; POST — запись, со стенда в боевой блог не пускает барьер контуров.
Ответ по каждому сервису отдельно, поле state. Google при чтении: submitted (карта известна: last_submitted, last_downloaded, is_pending, errors, warnings) или not_submitted; при отправке — submitted, Google перечитает карту заново. Яндекс при чтении: added (sitemap_id, added_date) или not_added; при отправке — added или already: карта уже добавлена, отдельного «перечитай» у API Вебмастера нет, добавленные карты он обходит сам. Отказы по сервису: not_connected (сервис не подключён к сайту), not_verified (сайт не подтверждён, 401/403 от API), api_error (code, message — как ответил сервис). Незнакомый service — 400 invalid_service, кривой sitemap_url — 400 invalid_sitemap_url. Итог проверки SEO несёт то же состояние в sitemap.consoles.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/sitemap/submit" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"service": "all"}'
{
"success": true,
"site_id": "SITE_ID",
"service": "all",
"sitemap": "https://example.com/sitemap_index.xml",
"gsc": { "state": "submitted", "sitemap": "https://example.com/sitemap_index.xml" },
"ywm": { "state": "already", "already": true, "sitemap": "https://example.com/sitemap_index.xml", "sitemap_id": "d1a3d870-ab57-32f5-bc04-fec6d92f3bb2" }
}
Адреса в карте сайта
GET /site/{siteId}/sitemap/urls — все адреса, которые сайт отдаёт поисковикам в карте: индекс sitemap_index.xml и каждая дочерняя карта из него, а не только карта страниц. Список читается с самого сайта в момент запроса, поэтому совпадает с XML строка в строку и идёт в том же порядке. Права — администратор сайта; тела и параметров нет.
В ответе: sitemap — адрес индекса; total — сколько адресов в картах; sitemaps — дочерние карты (url, path, lastmod, status, count; карта, которая не ответила, несёт error и count: 0); urls — адреса по порядку: loc, images (сколько картинок у адреса в карте), lastmod (время изменения, как в карте), sitemap (путь карты, где он стоит).
Отказы: индекс карты не ответил — 502 sitemap_unavailable (sitemap, status, при обрыве — reason); у сайта нет адреса — 409 site_home_missing.
curl -s "https://onpress.pro/api/v2/site/SITE_ID/sitemap/urls" \
-H "Authorization: Bearer op_XXXX"
{
"success": true,
"site_id": "SITE_ID",
"sitemap": "https://example.com/sitemap_index.xml",
"total": 73,
"sitemaps": [
{ "url": "https://example.com/page-sitemap.xml", "path": "/page-sitemap.xml", "lastmod": "2026-10-03T10:30:00+00:00", "status": 200, "count": 73 }
],
"urls": [
{ "loc": "https://example.com/", "images": 0, "lastmod": "2026-09-12T17:48:01+00:00", "sitemap": "/page-sitemap.xml" },
{ "loc": "https://example.com/primery/", "images": 2, "lastmod": "2026-09-12T18:04:47+00:00", "sitemap": "/page-sitemap.xml" }
],
"took_ms": 516
}
robots.txt
Без своего текста сайт отдаёт общий шаблон сети (закрыты wp-admin, ленты, поиск, /page/, плюс Sitemap: своей карты). Свой текст отдаётся как есть; строка Sitemap: дописывается, только если её в тексте нет. GET отвечает content (что сайт отдаёт сейчас), custom, default_content, url; PUT {"content": "…"} — то же плюс changed и warnings; DELETE возвращает шаблон сети. Лимит — 64 КБ.
Строка — пустая, # комментарий или Директива: значение; другая строка — 400 invalid_robots со списком errors (line, text, message). Незнакомая директива, правило до первого User-agent и Sitemap не абсолютным адресом запись не останавливают — они в warnings. Ещё отказы: 400 invalid_content (не строка), 400 empty_content (пусто — для этого есть DELETE), 413 too_large. У закрытого сайта robots.txt отдаёт Disallow: /, свой текст сохраняется.
Живой ответ файлов проверка SEO кладёт в files итога: robots.state — ok (отдаётся, есть текст), warn (закрывает весь сайт Disallow: /, так бывает у закрытого сайта), fail (не отдаётся, http); llms.state — ok, none (404, файл не собран) или fail.
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/robots" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"content": "User-agent: *\nDisallow: /private/\n"}'
{
"success": true,
"site_id": "SITE_ID",
"content": "User-agent: *\nDisallow: /private/\n\nSitemap: https://example.com/sitemap_index.xml\n",
"custom": true,
"url": "https://example.com/robots.txt",
"changed": true,
"warnings": []
}
llms.txt
Своего умолчания нет: без текста /llms.txt отвечает 404. GET /site/{siteId}/llms — content, exists, auto, url, site_closed; PUT {"content": "…"} — плюс changed, warnings (первая строка не # Название — предупреждение); DELETE снимает текст. Отказы — как у robots.txt: 400 invalid_content, 400 empty_content, 413 too_large.
Сборка. POST /site/{siteId}/llms/build собирает текст в формате llmstxt.org и сохраняет; {"dry_run": true} или GET /site/{siteId}/llms/build показывают результат без сохранения (content, pages, bytes, saved). В текст попадают опубликованные страницы и записи всех языков сайта; каждая ссылка ведёт на markdown-копию страницы — адрес с .md, где лежит чистый текст без шапки и меню. Не попадают страницы с noindex, без фрейма, под паролем, служебная 404 и страницы с неопубликованным родителем. Разделы: сначала страницы без раздела, потом по разделу на каждую корневую страницу с детьми (документация, новости, журнал изменений…), в конце записи. До 600 ссылок, из них записей до 100; если текст не укладывается в 64 КБ, сначала укорачиваются описания, потом хвосты разделов.
# Название сайта
## Pages
- [Тарифы](https://example.com/pricing.md): Выдержка страницы.
## Documentation
- [Documentation](https://example.com/docs.md): Выдержка раздела.
Автопересборка. Текст, собранный сборкой, помечается как автоматический (auto: true) и пересобирается сам после каждой публикации, правки, снятия с публикации, переноса и удаления страницы — llms.txt всегда совпадает с сайтом. Свой текст, сохранённый PUT, автоматическим не бывает и сам не меняется никогда; вернуть автоматику — снова вызвать сборку. DELETE снимает и текст, и пометку. Флаг «не индексировать сайт» (blog_public: 0) сборку не останавливает; у закрытого сайта /llms.txt отвечает 404, что бы ни было сохранено. Собрать разом и llms.txt, и все markdown-копии — POST /site/{siteId}/markdown/build.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/llms/build" -H "Authorization: Bearer op_XXXX"
{ "success": true, "site_id": "SITE_ID", "pages": 60, "bytes": 18140, "saved": true, "url": "https://example.com/llms.txt", "content": "# Название сайта\n\n## Pages\n\n- […](https://example.com/pricing.md): …" }
Дополнительно: статус в Google
Вердикт Search Console URL Inspection по странице: GET /site/{siteId}/seo/index-status?ref=… (одна страница — id, слаг или путь), POST {"refs": […до 100], "refresh", "lang"}. Поля: verdict, coverage_state, indexing_state, robots_txt_state, page_fetch_state, last_crawl_time, google_canonical, checked_at, source. Результат держится сутки, refresh — не чаще раза в час, квота — 1900 проверок в сутки на ресурс. Отказы: 409 gsc_not_connected (ресурс не подключён), 404 page_not_found, 409 not_published, 429 gsc_quota_exhausted с Retry-After, 503 gsc_busy.
curl -s "https://onpress.pro/api/v2/site/SITE_ID/seo/index-status?ref=5776" -H "Authorization: Bearer op_XXXX"
Счётчики
Яндекс Метрика, Google Analytics 4, Search Console и Яндекс Вебмастер одним пунктом counters итога проверки: статус — худший из четырёх, в полях — состояние каждого, строка в дашборде ведёт в «Аналитика → Счётчики».
Яндекс Метрика. POST /wp-json/onpress/v1/analytics/connect заводит счётчик под аккаунтом платформы и пишет его id в настройки сайта; тема ставит код на каждую страницу. service — metrika, ga, gsc или ywm; без него — 400 missing_service, другой — 400 unknown_service; сбой на стороне сервиса — 422. Пункт в порядке, когда у сайта записан id счётчика (ym_counter_id в GET /site/{siteId}/settings → analytics).
curl -s -X POST "https://example.com/wp-json/onpress/v1/analytics/connect" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"service": "metrika"}'
{
"success": true,
"site_url": "https://example.com/",
"metrika": { "configured": true, "connected": false, "enabled": false, "counter_id": "", "counter_name": "" },
"webmaster": { "connected": false, "verified": false, "site_url": "" },
"gsc": { "configured": true, "connected": false, "verified": false, "site_url": "" },
"ga": { "configured": true, "connected": false, "enabled": false, "measurement_id": "" }
}
Выше — ответ GET /wp-json/onpress/v1/analytics/status, состояние всех четырёх служб.
Google Analytics 4. Тот же connect с {"service": "ga"}. Пункт в порядке, когда есть ga_measurement_id.
Search Console. Подтверждение сайта — ключи analytics в PATCH /site/{siteId}/settings: gsc_verification_token (содержимое мета-тега подтверждения, тема ставит его в <head>), gsc_site_url (ресурс), gsc_verified. Пункт в порядке, когда есть токен и gsc_verified. Незнакомый ключ — 400 invalid_analytics; секрет ywm_client_token не пишется и не читается — 400 secret_field (его ставит вход через OAuth, снимает POST /wp-json/onpress/v1/analytics/disconnect). Непереданные ключи не трогаются. Подключить ресурс под аккаунтом платформы — connect с {"service": "gsc"}.
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/settings" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"analytics": {"gsc_verification_token": "abc123", "gsc_site_url": "https://example.com/", "gsc_verified": 1}}'
Яндекс Вебмастер. То же для Вебмастера: ywm_verification_token, ywm_site_url, ywm_verified, ещё ywm_host_id. Пункт в порядке, когда есть токен и ywm_verified. Подключение через аккаунт владельца — вход OAuth, его ключи ywm_client_* только читаются.
Трафик по страницам. Когда подключены Search Console или Google Analytics, раз в сутки по каждой странице собираются показы и клики Google, посетители GA (все и из поиска), время на странице и доля отказов по визитам, начавшимся на странице, за последние 12 месяцев, вместе с трафиком старых адресов страницы, с которых стоит редирект: GET /site/{siteId}/traffic. История страницы по дням, неделям и месяцам с датами правок — GET /site/{siteId}/pages/{ref}/traffic — трафик страниц. Рекомендации по каждой странице — что переписать, отправить на индексацию, где поправить сниппет или интент — поле advice того же ответа, рекомендации.
Контент
| Пункт | Ручка | Тело |
|---|---|---|
| Ключ в Title и Description | GET /site/{siteId}/seo/audit → keywords GET /site/{siteId}/pages/{ref}/markdown POST /site/{siteId}/pages/markdown | frontmatter keyword, seo_title, seo_description |
| Title и Description | GET /site/{siteId}/seo/pages → meta POST /site/{siteId}/pages/markdown | frontmatter seo_title, seo_description |
| Длина Title и Description | GET /site/{siteId}/seo/pages → lengths POST /site/{siteId}/pages/markdown | frontmatter seo_title, seo_description |
| Заголовки H1–H6 | GET /site/{siteId}/seo/audit → headings GET /site/{siteId}/seo/pages → headings POST /site/{siteId}/pages/markdown | тело страницы |
| Alt у картинок | GET /site/{siteId}/seo/audit → images-altGET /site/{siteId}/pages/{ref}/markdownPOST /site/{siteId}/pages/markdown |  в теле страницы |
| Закрытые страницы | GET /site/{siteId}/seo/pages → noindex POST /site/{siteId}/pages/markdown | frontmatter _yoast_wpseo_meta-robots-noindex |
| Контакты и документы | GET /site/{siteId}/seo/audit → contacts POST /site/{siteId}/pages/markdown PUT /site/{siteId}/data/footer | frontmatter страницы; {"items": […], "lang": "ru"} |
Пункт «Ключ стоит в Title и Description» считает проверка целиком (POST /site/{siteId}/seo/audit, пункт keywords) по страницам этого же обхода. Пункты обхода считает обход: GET /site/{siteId}/seo/pages проходит опубликованные страницы по HTTP и читает ту разметку, которую сервер отдаёт на самом деле — до 2000 страниц, 8 одновременно, 15 с на страницу. Параметр lang — язык (по умолчанию all); незнакомый — 422 unknown_language; ни у одной страницы нет адреса — 409 no_public_pages. Страницы с noindex на заголовки не проверяются, но перечислены; страница, открытая для индексации и отвечающая не 2xx, — в unreachable (reason: http_error, timeout, unreachable, not_html).
curl -s "https://onpress.pro/api/v2/site/SITE_ID/seo/pages?lang=ru" -H "Authorization: Bearer op_XXXX"
{
"success": true,
"site_id": "SITE_ID",
"lang": "ru",
"took_ms": 2808,
"totals": { "pages": 35, "crawled": 35, "checked": 30, "skipped_noindex": 5, "unreachable": 0 },
"meta": { "count": 0, "pages": [] },
"lengths": { "count": 22, "title_max": 65, "description_max": 165, "pages": [ { "id": 6231, "url": "/ru/docs/", "long": ["title"], "title_text": "…" } ] },
"headings": { "count": 3, "pages": [ { "id": 6234, "url": "/ru/docs/install/", "problems": [ { "kind": "many_h1", "count": 2, "text": "Установка" } ], "outline": ["…"] } ] },
"noindex": { "count": 5, "pages": [ { "id": 6240, "url": "/ru/errors/", "status": 404 } ] },
"unreachable": [],
"signals": { "pages": 30, "canonical": { "…": "…" }, "schema": { "…": "…" }, "images": { "…": "…" }, "preload": { "…": "…" }, "hreflang": { "…": "…" }, "og": { "…": "…" }, "redirects": { "…": "…" }, "not_found": { "…": "…" } }
}
Исправление пунктов обхода — публикация страницы с нужным frontmatter или телом:
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/pages/markdown" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"markdown": "---\nurl: \"/about/\"\nseo_title: \"О компании Example\"\nseo_description: \"Кто мы и что делаем — коротко.\"\n---\n\n# О компании\n\nТекст страницы."}'
Ключ в Title и Description
Так должно быть у каждой продвигаемой страницы: ключ целиком стоит в итоговом <title> и в <meta name="description">. Проверяются все опубликованные страницы обхода, у которых задан ключ; страницы без ключа не проверяются и ошибкой не считаются. Ключ — keyword во frontmatter, он же колонка «Ключ» раздела «Страницы» и ключевая фраза Yoast (_yoast_wpseo_focuskw; мета keyword читается, только когда поле Yoast пустое); несколько ключей — через запятую, точку с запятой или с новой строки. Title и Description сверяются с отданной разметкой, а не с метой Yoast: в мете лежат шаблоны, а фрейм может заголовок перекрыть. Сравнение фразой целиком по границам слов: без учёта регистра и диакритики (ё = е), кавычки, тире и знаки препинания отбрасываются, пробелы схлопываются; в китайском, японском, тайском и бирманском — вхождением без границ слов.
Статус: все страницы с ключом в порядке — ok, есть нарушения — todo, ключ не задан ни у одной страницы — na. Строка пункта: id, title, lang, url, keywords, title_ok, description_ok (null, когда страница не открылась — причина в reason), missing — какие ключи где не нашлись, seen — что стоит на странице (title, description), status, reason; нарушения идут первыми.
Исправление — выгрузить страницу, вписать ключ в seo_title и seo_description и опубликовать файл обратно целиком: публикация переписывает тело. Готовые команды по каждой странице — в fix пункта (до 25 страниц).
curl -s "https://onpress.pro/api/v2/site/SITE_ID/pages/6949/markdown" -H "Authorization: Bearer op_XXXX"
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/pages/markdown" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"markdown": "---\nid: 6949\nkeyword: \"чат бот\"\nseo_title: \"Чат бот онлайн без регистрации\"\nseo_description: \"Бесплатный чат бот: …\"\n---\n\n<тело из выгрузки без изменений>"}'
Заголовки H1–H6
На странице ровно один H1, и уровни идут без пропусков. Пункт headings итога проверки объединяет обе проверки: в полях — «Ровно один H1: N из N» и «Уровни без пропусков: N из N», в pages — страницы с нарушениями, статус — худший из двух.
Ровно один H1. headings.pages[].problems[] с kind no_h1 (H1 нет) или many_h1 (count, text второго). Навигация, подвал и боковая колонка не учитываются. H1 страницы — её name во frontmatter; второй # в теле — лишний H1.
Уровни без пропусков. problems[] с kind: "jump" — скачок уровня (from, to, text), например H2 → H4; рядом outline — лестница заголовков страницы. Чинится в теле страницы.
Alt у картинок
Пункт images-alt итога проверки считает картинки тела каждой открытой страницы из обхода (GET /site/{siteId}/seo/pages → pages[].images: alt_images, alt_missing, alt_missing_list) — без шапки сайта, меню, подвала и боковой колонки. Нет атрибута alt — ошибка; alt="" засчитывается только декоративной картинке (role="presentation" или aria-hidden="true"). Все с alt — ok, есть без alt — todo. В rows — картинки без alt: src, file, alt (null — атрибута нет, "" — пустой), page (id, url, link).
alt на v2 живёт в теле страницы: markdown  становится <img alt="alt">, а alt вложения подставляется при выводе, только когда атрибута нет вовсе. Поэтому alt вписывается в тело — выгрузить страницу, дописать alt в каждую  и опубликовать обратно.
curl -s "https://onpress.pro/api/v2/site/SITE_ID/pages/5776/markdown" -H "Authorization: Bearer op_XXXX"
# в теле: 
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/pages/markdown" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"markdown": "---\nid: 5776\n…\n---\n\n\n"}'
Title и Description
meta.pages[] — страницы с пустым <title> или meta description: missing (title, description), title_text, description_text. Чинится frontmatter публикации: seo_title и seo_description — их отдаёт Yoast в <title> и meta description.
Длина Title и Description
lengths.pages[] — title длиннее title_max (65) или описание длиннее description_max (165) знаков: long — что именно, тексты и их длины. Чинится теми же seo_title и seo_description.
Закрытые страницы
noindex.pages[] — все страницы, которые отдаются с noindex, с кодом ответа (служебные 404 отвечают 404). noindex отдельной страницы — frontmatter _yoast_wpseo_meta-robots-noindex: "1" — закрыть, "2" — открыть, "0" — как у типа записей.
Дополнительно: ссылки
Мёртвые ссылки и ссылки на страницу другого языка ищет фоновая проверка: POST /site/{siteId}/seo/links/check ставит прогон и сразу отвечает 202 (второй, пока идёт первый, — 409 check_running), GET /site/{siteId}/seo/links отдаёт ход или итог — status (none, running, done, failed, interrupted), dead, wrong_lang, unconfirmed, totals. Граф внутренних ссылок по телам страниц, сироты и вес — GET /site/{siteId}/seo/link-graph (lang, fresh=1; незнакомый язык — 422 unknown_language). Каждая ссылка из текста с анкором, контекстом и состоянием цели — ссылки страниц: собираются POST /site/{siteId}/links/build, читаются GET /site/{siteId}/links.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/seo/links/check" -H "Authorization: Bearer op_XXXX"
curl -s "https://onpress.pro/api/v2/site/SITE_ID/seo/links" -H "Authorization: Bearer op_XXXX"
curl -s "https://onpress.pro/api/v2/site/SITE_ID/seo/link-graph?lang=ru" -H "Authorization: Bearer op_XXXX"
Контакты и документы
По каждому языку должны быть опубликованы политика конфиденциальности, условия использования и страница контактов, а в подвале главной — почта или телефон (ссылка mailto: или tel:) и ссылки на оба документа. Документ находится по слагу страницы этого языка: privacy, privacy-policy, polite, politika…, политика…; terms, terms-of-use, oferta, оферта; contacts, contact, kontakty, контакты. Строка пункта: lang, is_default, privacy, terms, contacts ({id, title, url} или null), footer_contact, footer_legal, status, reason — чего не хватает.
Нет документа — опубликовать страницу; перевод документа основного языка — с translation_of:
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/pages/markdown" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"markdown": "---\nname: \"Политика конфиденциальности\"\nurl: /ru/privacy/\nlang: ru\nstatus: publish\ntranslation_of: 6100\n---\n\nТекст политики."}'
Нет контактов или ссылок в подвале — если подвал сайта собран из набора данных footer (с вариантом языка), прочитать GET /site/{siteId}/data/footer?lang=ru, дописать email, email_url и legal ([{"text", "link"}]) и положить обратно PUT /site/{siteId}/data/footer с тем же lang. Набора нет — 404 source_not_found, подвал тогда задан фреймом.
Schema-разметка
Группа показывает не настройки, а то, что сайт на самом деле отдаёт: узлы JSON-LD и теги Open Graph, снятые с живых страниц запросом без кук. Страницы-образцы выбираются по обходу проверки: главная (страница «/»), первая статья (узел Article, BlogPosting или NewsArticle), страница с вопросами (главная, если на ней есть FAQPage, иначе первая такая), страница автора (ProfilePage), страница с крошками (BreadcrumbList) и одна внутренняя. Граф Yoast и отдельные скрипты <script type="application/ld+json"> разбираются по @type; ссылка {"@id": …} на объект, которого нет на странице, считается ссылкой в никуда.
Статусы у всех пунктов группы одни: узел есть и обязательные поля заполнены — ok; узел есть, но обязательное поле пустое или ссылка @id ведёт в никуда — warn с перечнем; узла нет, хотя он нужен, — todo; к сайту не относится (нет страниц с вопросами, приложения, статей) — na; страница-образец не открылась — unknown.
| Пункт | Узел и обязательные поля | Чем чинится |
|---|---|---|
| WebSite | WebSite на главной: name, url, description | PATCH /site/{siteId}/settings → blogdescription |
| Organization | Organization или подтип на главной: name, url, logo; желательно sameAs | PATCH /site/{siteId}/seo → organization, social.profiles; если название задано в теме — POST /wp-json/onpress/v1/settings/organization и …/settings/socials |
| WebPage и Article | WebPage на главной: name, url, description; Article статьи: headline, author, datePublished | описание страницы — seo_description; статья — набор author |
| Автор | Person статьи: name; ProfilePage: mainEntity | PUT /site/{siteId}/data/author |
| Хлебные крошки | BreadcrumbList: itemListElement с name | вложенность страниц (url с родителем во frontmatter) |
| FAQPage | FAQPage: mainEntity, у вопроса name и acceptedAnswer.text, один узел на страницу | блок вопросов на странице |
| HowTo | HowTo: name, шаги HowToStep с text по порядку, один узел на страницу | режим howto страницы: PATCH /site/{siteId}/pages/{ref} |
| SoftwareApplication | SoftwareApplication: name и offers или aggregateRating | GET /site/{siteId}/seo/app PATCH /site/{siteId}/seo/app |
| Open Graph | og:title, og:description, og:url, og:type, og:image, twitter:card | PATCH /site/{siteId}/seo → social |
Строки пункта. В rows — по строке на страницу-образец: page (home, article, faq, profile, breadcrumbs, app, inner), label, id, lang, url — адрес, с которого снят код, status, title — тип узла, language (json или markup), code — узел JSON-LD как есть, отформатированный (несколько узлов — массивом), либо мета-теги из <head> строками; null — нужного узла на странице нет. missing — пустые обязательные поля, recommended — пустые желательные, refs — ссылки @id в никуда, note — почему страница не открылась. Длинный код режется на 24 КБ, длинный ряд og:locale:alternate сжат до трёх тегов и комментария с остатком.
WebSite
Узел WebSite на главной с name, url и description. Описание Yoast подставляет из описания сайта, и пустым оно бывает чаще всего. Поля пункта — name, description, адрес поиска по сайту из SearchAction, publisher и сколько страниц обхода несут узел. Описание пишется в настройки сайта:
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/settings" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"blogdescription": "Бесплатный ИИ-чат без регистрации"}'
Organization
Узел Organization (или LocalBusiness, Corporation и другой подтип) на главной: name, url, logo обязательны, sameAs — желательно, пустой показывается предупреждением в полях, но статус не портит. Узел строит кто-то один: пока в настройках темы пусто org_name, его выводит Yoast из раздела organization ручки /site/{siteId}/seo, а sameAs берёт из social.profiles; как только org_name заполнено, тема выводит свой узел (логотип — иконка сайта, sameAs — соцсети из POST /wp-json/onpress/v1/settings/socials) и убирает Organization Yoast из графа. Поле «Источник» говорит, какой из двух действует.
PATCH /site/{siteId}/seo, раздел organization: type — company или person (иначе 400 invalid_organization_type), name, alternate_name, logo — id картинки (нет такой — 400 invalid_logo). social.profiles: facebook, twitter (без @), instagram, linkedin, youtube, wikipedia, mastodon, pinterest и other — массив адресов; другая сеть — 400 unknown_field, other не массив — 400 invalid_profiles.
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/seo" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"organization": {"type": "company", "name": "Example", "logo": 4827}, "social": {"profiles": {"other": ["https://www.linkedin.com/company/example/"]}}}'
WebPage и Article
Узел страницы (WebPage, ProfilePage, CollectionPage…) на главной с name, url и description и, если на сайте есть статьи, узел Article/BlogPosting первой из них: headline, author, datePublished обязательны, image, dateModified, publisher — желательно. По всему обходу считаются битые скрипты JSON-LD и @id на чужом домене — любой из них даёт warn. Описание страницы — seo_description во frontmatter; BlogPosting тема добавляет странице, чей фрейм объявил набор author.
Автор
Person, на которого ссылается author статьи, с name (желательно url, sameAs, image, jobTitle), и страница автора ProfilePage с mainEntity на ту же персону. Персону тема собирает из набора author только на страницах, чей фрейм объявил набор. Пункт нужен, если на сайте есть статьи или набор заполнен: набор есть, а Person не выводит ни одна страница — todo («фреймы не объявляют набор»); нет ни статей, ни набора — na. Набор пишется PUT /site/{siteId}/data/author (поля элемента slug, name, role, bio, url, avatar_url, socials); без items — 400 bad_dataset_items, больше 512 КБ — 413 dataset_too_large. Контракт наборов — справочник данных.
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/data/author" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"items": [{"slug": "default", "name": "Анна Петрова", "role": "Редактор", "url": "https://example.com/anna/", "socials": ["https://www.linkedin.com/in/example/"]}]}'
Хлебные крошки
BreadcrumbList с itemListElement, у каждого звена — name. Узел строится на внутренних страницах сайта: цепочка «главная → родители → страница» по вложенности страниц, первое звено — главная языка страницы («Главная», «Home» и т. д. на языке страницы). Ту же цепочку страница показывает видимыми крошками — первой строкой подвала фрейма (или прямо над подвалом, если у фрейма нет строки site-footer__inner), так что разметка и видимые крошки не расходятся. Видимые крошки — всегда одна строка: «Главная» и разделители не сжимаются, длинные пункты обрезаются многоточием (средние раньше текущей страницы), полный текст пункта — во всплывающей подсказке (title). Статус пункта: узел найден и у звеньев есть name — ok, пустое звено — warn; узла нет ни на одной странице — todo, если на сайте есть вложенные страницы (два и больше сегмента пути без языкового префикса), и na на плоском сайте. В полях — сколько страниц с узлом и образец.
FAQPage
FAQPage с mainEntity, у каждого вопроса — name и acceptedAnswer.text. Узел собирается из блока вопросов на странице сам. Два FAQPage на одной странице — warn: такую страницу поисковик для расширенного ответа не берёт. Страниц с FAQPage нет вовсе — na. Образец — главная, если вопросы есть на ней.
HowTo
HowTo с name и шагами HowToStep: у шага name и text, адрес — якорь на странице, картинка — первая в разделе шага. Узел собирается из текста страницы сам: три и больше подзаголовков «Шаг 1…», «Step 2…» подряд или нумерованный список от трёх пунктов под H2/H3 со словом инструкции на языке страницы («как», «шаг», «инструкция», «how to», «step», «guide»). Режим страницы howto (auto, on, off) задаётся во frontmatter и в PATCH /site/{siteId}/pages/{ref}: on строит узел от двух шагов без слов в подзаголовке, off выключает. Узел встраивается в граф Yoast. Страниц с HowTo нет — na; пустое name, нет шагов, шаг без текста, номера шагов не по порядку или два узла на одной странице — warn. Шаги, размеченные фреймом steps, важнее поиска по тексту: первый вызов steps даёт узел и в режиме auto, от двух шагов, off гасит и его. В пункте — число страниц с узлом, число шагов образца и таблица страниц, где узел есть.
SoftwareApplication
SoftwareApplication (или WebApplication, MobileApplication): name и offers или aggregateRating; желательно applicationCategory, operatingSystem, description. Тема выводит узел только на главной, когда карточка приложения включена и заполнены название, оценка и число оценок. Узла нет ни на одной странице обхода и на живой главной — разметка не используется, na; узел пропал с главной или неполный — warn с готовой командой PATCH /site/{siteId}/seo/app.
Карточка приложения читается и правится ручкой /site/{siteId}/seo/app. PATCH принимает любое подмножество полей, null или "" очищают поле:
| Поле | Тип | Смысл |
|---|---|---|
enabled | bool | выводить узел; по умолчанию включено, выключенный узел не выводится при любых полях |
name | string | название приложения — обязательно для вывода |
description | string | описание |
category | string | applicationCategory, одно из choices.category; по умолчанию BrowserApplication |
operating_systems | string[] | платформы из choices.operating_systems (Chrome, Windows, iOS…); выходят строкой через запятую |
price | number | цена, 0 и больше; 0 — бесплатно |
currency | string | валюта из choices.currency; по умолчанию USD |
rating_value | number | оценка от 1 до 5 — обязательна для вывода |
rating_count | integer | число оценок, от 1 — обязательно для вывода |
Картинка узла — иконка сайта (поле icon в «Основе»), отдельно не задаётся.
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/seo/app" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"name": "Example", "category": "WebApplication", "operating_systems": ["Chrome", "Windows"], "price": 0, "currency": "USD", "rating_value": 4.8, "rating_count": 127}'
{
"success": true,
"site_id": "SITE_ID",
"changed": ["name", "category", "operating_systems", "price", "rating_value", "rating_count"],
"app": {
"enabled": true,
"name": "Example",
"description": "",
"category": "WebApplication",
"operating_systems": ["Chrome", "Windows"],
"price": 0,
"currency": "USD",
"rating_value": 4.8,
"rating_count": 127,
"image": "https://example.com/wp-content/uploads/icon-512.png",
"missing": [],
"output": true,
"choices": {
"category": { "BrowserApplication": "Browser Application", "BusinessApplication": "Business Application", "EducationalApplication": "Educational Application", "UtilitiesApplication": "Utilities Application", "WebApplication": "Web Application" },
"operating_systems": { "Chrome": "Google Chrome", "Windows": "Windows", "…": "…" },
"currency": { "USD": "USD", "EUR": "EUR", "GBP": "GBP", "RUB": "RUB" }
}
},
"cache": { "…": "…" }
}
GET отдаёт app в той же форме без changed и cache. missing — каких из трёх обязательных полей не хватает, output — выйдет ли узел на главной (включён и ничего не пропущено), choices — допустимые значения категории, платформ и валюты, их задаёт тема сайта. changed называет только реально изменившиеся поля. После записи кэш страниц сбрасывается, узел виден на главной сразу.
Отказы: 400 nothing_to_change (в теле ни одного поля), 400 unknown_field (поле вне списка), 400 invalid_enabled, 400 invalid_name, 400 invalid_description, 400 invalid_category, 400 invalid_currency (значение вне choices), 400 invalid_operating_systems (не массив или платформа вне choices), 400 invalid_price, 400 invalid_rating_value, 400 invalid_rating_count, 409 theme_fields_missing (тема сайта не знает настроек приложения).
Open Graph
На главной и одной внутренней странице (статья, если есть) — og:title, og:description, og:url, og:type, og:image и twitter:card; код строки — сами теги og:, twitter:, article: в порядке <head>. Нет ни og:title, ни og:type — todo; не хватает тега или og:image нет у части страниц обхода — warn, первые десять таких страниц — в pages. Теги выводит Yoast: раздел social ручки PATCH /site/{siteId}/seo — open_graph, twitter (bool), twitter_card — summary или summary_large_image (иначе 400 invalid_twitter_card), default_image — id картинки по умолчанию (нет такой — 400 invalid_default_image).
curl -s -X PATCH "https://onpress.pro/api/v2/site/SITE_ID/seo" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"social": {"open_graph": true, "default_image": 4827}}'
Скорость
| Пункт | Ручка | Тело |
|---|---|---|
| WebP | GET /site/{siteId}/seo/audit → auto-webp | встроено |
| Предзагрузка первой картинки | GET /site/{siteId}/seo/pages → signals.preload | встроено |
| Web Vitals | GET /wp-json/onpress/v1/metrics/site → vitals | — |
| Кэш | GET /site/{siteId}/seo/audit → page-cache | встроено |
WebP
Картинки отдаются в WebP через <picture>, настраивать нечего. Есть картинки не в WebP — пункт warn, а не ok; в итоге проверки пункт называется auto-webp.
Поиск страниц с такими картинками входит в общую проверку: POST /site/{siteId}/seo/audit сохраняет их в пункте, и GET отдаёт их без нового обхода. В pages — до 50 страниц, больше всего картинок не в WebP сверху: id, lang, url, link, в note — сколько картинок на странице не в WebP; всего таких страниц — pages_total. Отдельно звать обход ради списка не нужно.
{
"id": "auto-webp",
"group": "Speed",
"status": "warn",
"builtin": true,
"pages": [
{ "id": 6292, "title": "", "lang": "en", "url": "/pricing/", "link": "https://example.com/pricing/", "note": "2" }
],
"pages_total": 59
}
Те же данные в обходе GET /site/{siteId}/seo/pages — signals.images: img (сколько <img>), webp (сколько из них в WebP) и without_webp: {count, pages}, у каждой страницы img и not_webp.
Предзагрузка первой картинки
Картинка первого экрана получает preload и fetchpriority="high". Подтверждение — signals.preload: у скольких страниц есть предзагрузка картинки и приоритет.
Web Vitals
Статус — по порогам CrUX по p75 (телефоны, если есть): все метрики «хорошо» — ok, есть «нужно улучшить» — warn, есть «плохо» — todo (LCP хорошо ≤ 2,5 с, плохо > 4 с), данных CrUX нет — na.
Полевые LCP, INP и CLS домена из Chrome UX Report — vitals ответа GET /wp-json/onpress/v1/metrics/site, по телефонам и остальным устройствам. Пока у сайта мало трафика, CrUX данных не даёт — vitals: null.
curl -s "https://example.com/wp-json/onpress/v1/metrics/site" -H "Authorization: Bearer op_XXXX"
Кэш
Платформа держит готовые копии страниц — настраивать ничего не нужно. Проверка: главная и две открытые страницы обхода, по два захода без кук; второй обязан отдать копию WP Super Cache (маркер в конце HTML). В rows — адрес, status, ttfb_ms второго захода; в полях — сколько страниц отдаётся статикой из дерева публикации. Хоть одна страница не из кэша — warn.
Локализация
Как языковые версии сайта связаны между собой. На сайте с одним языком группы в чеклисте нет, а «Главные страницы» стоят последним пунктом «Основы».
| Пункт | Ручка | Тело |
|---|---|---|
| Главные страницы | GET /site/{siteId}/seo/audit → homes GET /site/{siteId}/data/settings/front_page PUT /site/{siteId}/data/settings/front_page | {"value": 5776} |
| Карта сайта с hreflang | GET /site/{siteId}/seo → sitemap.index_url | встроено |
| hreflang | GET /site/{siteId}/seo/pages → signals.hreflang | встроено |
| x-default | GET /site/{siteId}/seo/pages → signals.home | встроено |
Главные страницы
У каждого языка своя главная, и пункт homes в проверке целиком даёт строку на язык: lang, is_default, id, title, url, status, reason. Строка в порядке, когда по / сайт показывает страницу, у языка она есть в группе переводов главной, опубликована, на v2 у неё есть фрейм (без фрейма v2 отвечает «не найдено») и её адрес отвечает 200 без редиректа этой самой страницей. Иначе reason: posts (вместо страницы — лента записей), noPage (главной на языке нет), notPage, notPublished, noFrame, wrongPage (по адресу другая страница), redirect, closed (сайт закрыт), unreachable, http. Чем чинить: нет главной сайта — поле front_page; нет главной языка — POST /site/{siteId}/pages/markdown с translation_of: <id главной> и lang; нет фрейма — PATCH /site/{siteId}/pages/{ref} {"frame": "…"}; по адресу другая страница или редирект — POST /site/{siteId}/cache/flush {"polylang": true, "supercache": true}.
Поле front_page — главная сайта. value при чтении — page_on_front (0, пока по / отдаётся лента записей), рядом — show_on_front, title, url и languages ({язык: id} главной каждого языка). Запись: value — id или путь опубликованной страницы, для многоязычного сайта рядом можно передать lang — слаг языка, которому принадлежит страница; с Polylang главные всех языков переезжают вместе с группой переводов. Отказы: 400 invalid_front_page (пусто или 0), 400 invalid_lang, 404 page_not_found, 409 page_not_published (черновик).
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/data/settings/front_page" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"value": 5776, "lang": "en"}'
{
"success": true,
"site_id": "SITE_ID",
"field": "front_page",
"value": 5776,
"show_on_front": "page",
"title": "home",
"url": "/",
"languages": { "en": 5776, "ru": 6037 },
"changed": false,
"before": { "show_on_front": "page", "page_on_front": 5776 },
"forwards_to": "PUT /site/front-page {page, lang?}",
"rebaked": { "status": "nothing", "reason": "the value did not change" }
}
Главная языка — это перевод главной страницы. Привязать уже опубликованную страницу как главную языка:
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/pages/5776/translations/ru" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"page": 6037}'
Список языков — поле languages, только чтение: [{slug, locale, name, default, front_page, url}] и рядом default. PUT /site/{siteId}/data/settings/languages отвечает 405 field_written_elsewhere и называет ручки языков: POST /site/{siteId}/languages добавляет язык, PATCH /site/{siteId}/languages/{slug} правит, PUT /site/{siteId}/languages/default меняет язык по умолчанию, DELETE /site/{siteId}/languages/{slug}?force=1 снимает — подробно в справочнике языков.
Карта сайта с hreflang
Карта Yoast с альтернативами языков. Адрес — sitemap.index_url в GET /site/{siteId}/seo.
hreflang
Ссылки на переводы в <head> каждой страницы. Подтверждение — signals.hreflang: сколько страниц их несут и у скольких нет обратной ссылки (missing_return). На одноязычном сайте пункт пуст.
x-default
Альтернатива для остальных языков указывает на язык по умолчанию. Подтверждение — signals.home.x_default обхода или язык по умолчанию сайта.
Позиции в выдаче
Места сайта в выдаче Google и Яндекса по ключевым запросам; позиции снимает SerpApi. На этих ручках стоит страница «Позиции» раздела SEO дашборда (/seo/positions). Права — администратор сайта. Способ оплаты, свой ключ и суточные счётчики принадлежат пользователю — владельцу токена — и общие для всех его сайтов; запросы и история позиций принадлежат сайту.
Два способа. credits (по умолчанию) — позицию снимает ключ платформы, за каждую снятую позицию списывается 1 кредит, в том числе когда сайт не нашёлся в пределах глубины. Кредит сначала резервируется, списывается после сохранения позиции и возвращается, если съём не удался. own — свой ключ SerpApi: платформа ничего не списывает, тратятся поиски вашего тарифа SerpApi. Один поиск — страница из десяти результатов; съём останавливается, как только в выдаче нашёлся адрес сайта или его поддомена, поэтому позиция в топ-10 стоит один поиск, а запрос глубиной 30 — до трёх. Позиция за пределами глубины сохраняется как position: null — «вне топа».
Лимиты действуют в обоих способах и не зависят от остатка в SerpApi; ответы отдают их в limits:
| Что | Предел |
|---|---|
| Запросов на сайт | 50 |
| Снятых позиций в сутки на пользователя | 50 — плановые и ручные вместе, сутки по UTC |
| Как часто снимается запрос | не чаще раза в неделю: interval_days от 7 до 90 |
| Ручная проверка одного запроса | раз в 60 минут |
| Ручных проверок на пользователя | 10 в час, по одной за раз |
| Глубина | за кредиты — до 30, свой ключ — до 100 |
| Одновременных съёмов на сервере | 2, ещё 20 ждут в очереди |
| Пункт | Ручка | Тело |
|---|---|---|
| Способ и плановая проверка | GET /site/{siteId}/serp/settings PUT /site/{siteId}/serp/settings | {"mode": "credits", "auto": false} |
| Свой ключ SerpApi | PUT /site/{siteId}/serp/key DELETE /site/{siteId}/serp/key | {"api_key": "…"} |
| Запросы | GET /site/{siteId}/serp/queries POST /site/{siteId}/serp/queries DELETE /site/{siteId}/serp/queries/{id} | {"query": "чат бот", "lang": "ru"} |
| Ключи страниц в запросы | POST /site/{siteId}/serp/queries/sync | — |
| История позиций | GET /site/{siteId}/serp/queries/{id}/positions | ?limit=100 |
| Снять позицию сейчас | POST /site/{siteId}/serp/queries/{id}/check | — |
Способ и плановая проверка
GET /site/{siteId}/serp/settings — способ mode (credits или own), плановая проверка auto, свой ключ key (null, пока не привязан), platform.configured — есть ли на сервере ключ платформы, credits — цена позиции (price), баланс (balance, available) и topup_path — страница пополнения, limits и budget — расход. ?refresh=1 заодно перечитывает остаток поисков своего ключа в SerpApi; поиск на это не тратится.
PUT /site/{siteId}/serp/settings принимает mode и auto (оба необязательны) и отвечает тем же, что GET. Плановая проверка раз в сутки снимает все запросы пользователя, у которых подошёл срок по interval_days: сначала ни разу не снятые, потом взятые из ключей страниц, потом самые давние. По умолчанию она выключена: за кредиты она тратит их без вашего участия. Смена способа без auto в теле переключает и её: own включает, credits выключает. Своим ключом плановая проверка тратит не больше равной доли остатка поисков до сброса тарифа и оставляет 10 поисков на ручные проверки.
budget: mode, auto, queries_total (запросов у пользователя на всех сайтах), planned_checks_month и planned_checks_month_site (сколько съёмов в месяц выйдет по интервалам — всего и на этом сайте), checks_today, manual_last_hour, allowance_today (сколько ещё можно снять сегодня). За кредиты ещё price, planned_credits_month (0, пока плановая проверка выключена), credits_available, credits_known. Своим ключом — searches_total, searches_left, searches_used, days_left до сброса тарифа, reserve, per_day, spent_today, planned_searches_month, planned_searches_until_reset, fits (хватит ли остатка до сброса) и max_queries_weekly (сколько запросов тариф выдержит раз в неделю).
Отказы: 422 invalid_mode (не credits и не own), 422 invalid_auto (не bool), 409 own_key_required (own без привязанного ключа).
curl -s "https://onpress.pro/api/v2/site/SITE_ID/serp/settings" -H "Authorization: Bearer op_XXXX"
{
"success": true,
"mode": "credits",
"auto": false,
"key": null,
"platform": { "configured": false },
"credits": { "action": "serp_position", "price": 1, "available": 20, "balance": 20, "topup_path": "/settings/credits" },
"limits": { "max_queries_per_site": 50, "max_checks_per_day": 50, "manual_cooldown_minutes": 60, "manual_checks_per_hour": 10, "min_interval_days": 7, "platform_max_depth": 30, "own_max_depth": 100, "concurrency": 2, "queue_max": 20 },
"budget": { "mode": "credits", "auto": false, "queries_total": 0, "planned_checks_month": 0, "planned_checks_month_site": 0, "checks_today": 0, "manual_last_hour": 0, "price": 1, "planned_credits_month": 0, "credits_available": 20, "credits_known": true, "allowance_today": 50 }
}
Свой ключ SerpApi
У пользователя один ключ на все сайты, ротации между ключами нет; новый PUT заменяет прежний. api_key — приватный ключ SerpApi, от 32 до 128 латинских букв и цифр. При привязке ключ проверяется бесплатным запросом сведений об аккаунте SerpApi, поиск не тратится. Ключ хранится зашифрованным и обратно не отдаётся: в ответе key — mask (•••• и последние четыре знака), status (ok, exhausted — поиски кончились, invalid — SerpApi ключ не принимает), status_message, plan, searches_total, searches_left, searches_used, hourly_limit, renewal_date, refreshed_at. Ответ — как у GET /site/{siteId}/serp/settings.
DELETE /site/{siteId}/serp/key отвязывает ключ, способ возвращается на credits, плановая проверка своим ключом выключается; история позиций остаётся.
Отказы: 422 invalid_api_key (не той формы или SerpApi его отверг), 502 serpapi_unavailable (SerpApi не ответил на проверку), 404 key_not_found (отвязывать нечего), 503 serp_not_configured (на сервере не настроено хранение ключей).
curl -s -X PUT "https://onpress.pro/api/v2/site/SITE_ID/serp/key" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"api_key": "ВАШ_КЛЮЧ_SERPAPI"}'
Запросы
GET /site/{siteId}/serp/queries — queries (сначала взятые из ключей страниц), depths — какие глубины доступны в текущем способе (за кредиты [10, 20, 30]), limits, budget. Запрос: id, query, engine, gl, hl, device, lang, page_id, source (page — из ключа страницы, manual — добавлен руками), depth, interval_days, last_checked_at, last_error, checked (снимался ли), position и url последнего съёма, previous_position, delta (на сколько мест поднялся: положительное — вверх; null, если одна из позиций вне топа), history — последние 12 съёмов {at, position}, expected_searches_per_month.
POST /site/{siteId}/serp/queries добавляет запрос и отвечает {"query": {…}}:
| Поле | Значение |
|---|---|
query | текст от 2 до 400 знаков, обязательно |
lang | язык страницы; из него берутся страна и язык выдачи: ru → ru/ru, en → us/en; без него — us/en |
gl, hl | страна (две буквы) и язык выдачи, если нужны не те, что дал lang |
engine | google (по умолчанию) или yandex |
device | desktop (по умолчанию) или mobile |
depth | 10, 20, 30, 50 или 100, по умолчанию 30; за кредиты — не больше 30 |
interval_days | от 7 до 90, по умолчанию 7 |
page_id | страница сайта, к которой относится запрос |
Отказы: 422 invalid_query, invalid_engine, invalid_gl, invalid_hl, invalid_depth (поле — в field), 422 depth_not_allowed (глубже 30 за кредиты, max), 422 invalid_interval (min), 409 query_exists (такой запрос с тем же поисковиком, страной, языком и устройством уже есть; id), 409 too_many_queries (на сайте уже 50, limit).
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/serp/queries" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"query": "чат бот онлайн", "lang": "ru"}'
{
"success": true,
"query": { "id": 23, "query": "чат бот онлайн", "engine": "google", "gl": "ru", "hl": "ru", "device": "desktop", "lang": "ru", "page_id": null, "source": "manual", "depth": 30, "interval_days": 7, "last_checked_at": null, "last_error": "", "checked": false, "position": null, "url": null, "previous_position": null, "delta": null, "history": [], "expected_searches_per_month": 12.9 }
}
DELETE /site/{siteId}/serp/queries/{id} снимает запрос вместе с историей, {"id": 23}; чужой или несуществующий — 404 query_not_found.
Ключи страниц в запросы
POST /site/{siteId}/serp/queries/sync берёт первый ключ каждой опубликованной страницы (keyword во frontmatter, он же ключ Yoast) и заводит запрос на языке страницы. Запросы, чей ключ со страницы пропал, выключаются, история остаётся. Сверх 50 запросов на сайт не добавляет. Ответ: pages_with_keyword, added, kept, disabled, skipped_over_limit, limit.
История позиций
GET /site/{siteId}/serp/queries/{id}/positions — query и positions от старых к новым: at, position (null — вне топа), url — найденный адрес сайта, searches — сколько поисков ушло. limit — по умолчанию 100, не больше 500.
Снять позицию сейчас
POST /site/{siteId}/serp/queries/{id}/check снимает позицию сразу; платит тот, кто вызвал: кредитом или поиском своего ключа. Ответ: searches, credits (1 за кредиты, 0 своим ключом), mode и query с новой позицией.
Отказы, ничего не снято и не списано: 402 insufficient_credits (required, available, topup_path), 429 check_cooldown (запрос уже проверяли руками меньше 60 минут назад; retry_after_sec, cooldown_minutes), 429 manual_hourly_limit (10 ручных за час; retry_after_sec), 429 daily_limit_reached (50 за сутки; limit, used), 409 check_in_progress (идёт другая ваша проверка), 429 queue_full (очередь сервера полна; retry_after_sec), 503 platform_key_missing (на сервере не настроен ключ платформы — снимать можно только своим ключом), 503 credits_unavailable, 409 no_own_key, 409 invalid_api_key (привязанный ключ перестал работать), 409 no_searches_left (поиски кончились, в том числе посреди съёма — тогда результат не сохраняется), 502 serpapi_failed (kind: throttled, network, failed), 404 query_not_found. Причина последнего отказа остаётся в last_error запроса.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/serp/queries/23/check" -H "Authorization: Bearer op_XXXX"
{ "success": false, "error": "platform_key_missing", "message": "…" }
Отправка на индексацию
Платная отправка страниц сайта в поиск: страница уходит в Google, Яндекс и Bing, одна отправка страницы — 2 кредита, для всех пользователей одинаково. В дашборде это кнопка «Отправить на индексацию» в строке раздела «Страницы», а всё отправленное видно в журнале «Инструменты → Индексация». Права — администратор сайта; кредиты списываются с того, кто отправляет, журнал общий для сайта.
Правила. Принимаются только адреса этого сайта, чужие возвращаются в rejected с причиной (foreign_host, invalid_url, invalid_scheme, not_a_string). До 2000 адресов за вызов. Страница, уже отправленная за последние 7 дней, пропускается и попадает в already_sent, если не передать force: true. Сумма резервируется перед отправкой, а списывается только за страницы, которые ушли; ничего не отправляется без подтверждённой суммы. Против злоупотреблений есть суточный предел на владельца и на сайт (platform.daily_limit). Индексацию поисковики не гарантируют.
| Пункт | Ручка | Тело |
|---|---|---|
| Цена и баланс | GET /site/{siteId}/indexing/account | — |
| Страницы сайта | GET /site/{siteId}/indexing/pages | ?lang=ru&per_page=50 |
| Оценка стоимости | POST /site/{siteId}/indexing/estimate | {"urls": ["…"]} |
| Отправка | POST /site/{siteId}/indexing/submit | то же и "confirm": true, "expected_cost": 2 |
| Журнал | GET /site/{siteId}/indexing/submissions POST /site/{siteId}/indexing/submissions/refresh | ?status=done&page=1 |
Цена и баланс
GET /site/{siteId}/indexing/account — в platform: enabled (отправка доступна), price_per_page — цена страницы в кредитах, unit: "credits", credits (balance, held — в резерве, available), top_up_path — страница пополнения, daily_limit (user, site).
{
"success": true,
"platform": { "enabled": true, "price_per_page": 2, "unit": "credits", "credits": { "balance": 20, "held": 0, "available": 20 }, "top_up_path": "/settings/credits", "daily_limit": { "user": 500, "site": 1000 } }
}
Страницы сайта
GET /site/{siteId}/indexing/pages — опубликованные страницы, свежие правки сверху: id, title, url, lang, modified, noindex (закрыта от индексации или паролем), state — new (ни разу не отправлялась), changed (правилась после отправки), sent; suggested — новая или изменённая и открытая. Параметры: lang (по умолчанию все), search, page, per_page (до 100, по умолчанию 50).
Оценка стоимости
POST /site/{siteId}/indexing/estimate считает сумму и ничего не отправляет. Тело: urls — полные адреса страниц сайта, необязательно force.
Ответ: host, pages — сколько страниц уйдёт, estimated_cost — сумма в кредитах (pages × price_per_page), unit: "credits", price_per_page, credits_available, enough, top_up_path, already_sent (url, at), rejected.
Отказы: 400 urls_required, 413 too_many_urls (больше 2000), 400 no_urls (ни одного адреса этого сайта; host, rejected), 400 platform_only (передан mode, отличный от platform), 503 credits_unavailable.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/indexing/estimate" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"urls": ["https://example.com/", "https://example.org/x/"]}'
{
"success": true,
"host": "example.com",
"mode": "platform",
"pages": 1,
"estimated_cost": 2,
"unit": "credits",
"price_per_page": 2,
"already_sent": [],
"rejected": [ { "url": "https://example.org/x/", "reason": "foreign_host" } ],
"credits_available": 20,
"enough": true,
"top_up_path": "/settings/credits"
}
Отправка
POST /site/{siteId}/indexing/submit — то же тело плюс confirm: true и expected_cost — сумма из оценки; необязательный idempotency_key защищает от двойной отправки при повторе запроса. Сервер считает сумму заново и отправляет, только если подтверждённая не меньше: без confirm — 409 confirmation_required, expected_cost меньше суммы — 409 cost_changed; в обоих случаях в теле отказа estimate с текущей оценкой.
Ответ: id — номер отправки в журнале, pages — сколько страниц ушло, charged — сколько списано, unit: "credits", already_sent, rejected.
Отказы: 409 nothing_to_send (всё уже отправлено за 7 дней; already_sent), 402 insufficient_credits (не хватает кредитов; required, available, top_up_path, estimate), 429 daily_limit (суточный предел; limit, used, left), 409 submit_in_progress (идёт другая ваша отправка), 409 duplicate_submission (idempotency_key уже использован — ничего не отправлено и не списано), 503 platform_unavailable (отправка сейчас недоступна), 502 send_failed (отправка сорвалась на середине: ушедшие страницы списаны, pages и charged говорят сколько), а также все отказы оценки. Порядок проверок: подтверждение, сумма, суточный предел, баланс — при любом отказе ничего не отправлено и не списано.
curl -s -X POST "https://onpress.pro/api/v2/site/SITE_ID/indexing/submit" \
-H "Authorization: Bearer op_XXXX" -H 'Content-Type: application/json' \
-d '{"urls": ["https://example.com/"], "confirm": true, "expected_cost": 2}'
{ "success": true, "id": "8f0c…", "mode": "platform", "pages": 1, "charged": 2, "unit": "credits", "already_sent": [], "rejected": [] }
Журнал
GET /site/{siteId}/indexing/submissions — отправки сайта, новые сверху, строка на отправку: id, url (первая страница отправки), pages, status, charged (кредитов; null — отправка не за кредиты), created_at, updated_at. Рядом summary_30d за последние 30 дней: sends, pages, done, charged. Параметры: page, per_page (до 200, по умолчанию 50), status, url (отправки этой страницы), search (часть адреса); refresh=1 сначала обновляет незавершённые отправки (итог — в refreshed).
Статусы: sent — отправлено, active — в работе, done — готово, failed — ошибка. Неверный status — 400 invalid_status. POST /site/{siteId}/indexing/submissions/refresh обновляет незавершённые отправки сразу: {"checked": 0, "updated": 0}.
curl -s "https://onpress.pro/api/v2/site/SITE_ID/indexing/submissions?refresh=1" -H "Authorization: Bearer op_XXXX"
{
"success": true,
"total": 1,
"page": 1,
"per_page": 50,
"total_pages": 1,
"summary_30d": { "sends": 1, "pages": 1, "done": 1, "charged": 2 },
"items": [ { "id": "8f0c…", "url": "https://example.com/", "pages": 1, "status": "done", "charged": 2, "created_at": "2026-10-06T09:06:09Z", "updated_at": "2026-10-06T12:00:00Z" } ]
}
Настройки Yoast целиком
GET /site/{siteId}/seo отдаёт всё, что Yoast хранит для сайта; PATCH /site/{siteId}/seo правит любые разделы — шлите только меняемые. Ответ записи — changed, seo после записи и cache.
| Раздел | Ключи |
|---|---|
separator | ключ разделителя: sc-dash, sc-pipe, sc-middot… |
titles | home, page, post, 404, author, archive, search |
metadesc | home, page, post, author, archive |
attachments | redirect_to_parent |
breadcrumbs | enabled, bold_last, separator, home, prefix |
organization | type (company, person), name, alternate_name, person_name, person_user_id, logo (id), person_logo (id) |
social | open_graph, twitter, twitter_card (summary, summary_large_image), default_image (id), profiles |
sitemap | enabled, author, entries_per_page (1–10000) |
Отказы: 400 nothing_to_change (пустое тело), 400 unknown_field (незнакомый раздел или ключ, allowed), 400 invalid_* по разделу, 409 yoast_missing (Yoast не активен).