Страницы
Эндпоинты страниц с параметрами, ответами и кодами отказов; как устроены страницы, написано на странице «Страницы», свойства страницы — на странице «Markdown».
Во всех примерах API — https://onpress.pro/api/v2, SITE — id сайта (SITE_ID), OP_TOKEN — токен op_XXXX.
Все эндпоинты — под $API/site/$SITE, право — администратор сайта. {ref} — id страницы, слаг или путь с ~ вместо / (ru~o-nas); голый слаг на многоязычном сайте ищется в языке из lang, без него — в основном языке. Любая запись страницы — публикация, точечная правка, правка полей, удаление — сама сбрасывает кэш этой страницы по её адресу и по прежнему, если адрес сменился, и отвечает полем cache: {"status": "flushed", "urls": [...]}, у черновика без адреса — nothing. Отказ приходит в форме {"success": false, "error": "<код>", "message": "…"} с дополнительными полями из таблицы отказов.
POST /pages/markdown
Создаёт страницу из markdown или обновляет существующую: эндпоинт ищет страницу по id в свойствах, а без него — по полному пути из url и языку. Все свойства страницы, включая url, name, lang и status, эндпоинт читает только из блока свойств в начале markdown, рядом с markdown в теле запроса стоят лишь поля из таблицы ниже. Чтобы не затереть чужую правку, передайте if_modified — версию modified, которую вы читали. Например, опубликовать страницу из файла page.md:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
markdown | string | — | обязательно; весь документ со свойствами |
if_modified | string | — | modified из GET /pages/{ref} или GET /pages/{ref}/markdown; страница с тех пор менялась — 409 page_changed, запись не идёт |
frame | string | — | фрейм страницы, слаг или id; перекрывает свойство frame |
blocks | array | — | дополнительные блоки после тела: core/*, onpress/*, {"type": "raw", "content": "…"} |
faq_autodetect | bool | false | собрать раздел FAQ в блок onpress/faq-items |
faq_markers | array | — | заголовки раздела FAQ для faq_autodetect |
curl -s -X POST "$API/site/$SITE/pages/markdown" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"markdown": "---\nname: \"Первая страница\"\nurl: \"/start/\"\nimage: 5829\n---\n\nТекст страницы."}'
{
"success": true,
"updated": false,
"id": 6446,
"status": "publish",
"lang": "ru",
"parent": 0,
"url": "/start/",
"link": "https://example.com/ru/start/",
"visible": true,
"translations": { "ru": 6446 },
"printed": { "how": "on_request", "url": "/start/", "link": "https://example.com/ru/start/", "frame": { "id": "a2dc0bc0d2da6961", "slug": "start", "renders_on_request": true } },
"lang_changed": { "changed": true, "from": null, "to": "ru" },
"warnings": [],
"edit_url": "https://onpress.pro/dashboard/pages?site=SITE_ID&page=6446",
"view_url": "https://example.com/ru/start/",
"preview_url": null,
"scheduled": null,
"cache": { "status": "flushed", "urls": ["https://example.com/ru/start/"] },
"meta": { "name": "Первая страница", "slug": "start", "…": "…" },
"blocks": [ { "blockName": "core/paragraph", "attrs": {}, "innerHTML": "…" } ]
}
| Поле | Когда есть | Смысл |
|---|---|---|
updated | всегда | true — обновлена существующая |
id, status, lang, parent | всегда | записанная страница |
url, link | всегда | путь в дереве и полный адрес (null у неопубликованной) |
visible | всегда | видит ли посетитель: опубликованы она и все предки |
translations | всегда | группа переводов {язык: id} |
translation_group | с translation_of | {origin, term_id, created, changed, unlinked} |
frame | во входе был frame | {id, slug, meta_key} или {cleared: true} |
printed | всегда | {how, url, link, frame, published_by}; how — static, on_request, not_published, theme |
slug_changed | слаг занят | {requested, applied, reason} |
lang_changed | язык страницы сменился | {changed, from, to} |
data_keys | в свойствах были свои ключи | массив своих ключей, которые запись сохранила в данные страницы (page.data.*), в той форме, в какой их прислали; без keyword и ключей с _ |
data_keys_note | вместе с data_keys | строка для агента: эти ключи сохранены как данные страницы, а не как ее свойства, и если вы имели в виду свойство, проверьте его имя. Это не предупреждение и не попадает в warnings |
date, date_gmt | всегда | дата публикации: время сайта и тот же момент в UTC (ISO 8601) |
scheduled | всегда | null или у future — {at, date, event}: момент выхода в UTC, он же по времени сайта и подтвердил ли сайт событие публикации |
redirects_created, auto_redirects | у опубликованной страницы сменился адрес | заведённые автоправила [{id, from, to}] и сводка {moves, created, updated, enabled} |
edit_url, view_url, preview_url | всегда | карточка страницы в консоли, адрес страницы, адрес предпросмотра (null у опубликованной) |
cache | всегда | сброс кэша страницы |
warnings | всегда | предупреждения |
meta, blocks | всегда | разобранные свойства и блоки, в которые превратилось тело |
Превью страницы задает свойство image: номер вложения этого сайта или адрес картинки. Адрес своей картинки узнаётся без скачивания, чужая картинка заливается в медиатеку, повтор того же адреса берёт то же вложение. image: "" снимает превью, нет ключа — превью не трогается. Превью — это og:image страницы, обложка в блоке onpress/featured-image и картинка в карточках дочерних страниц.
Пошаговая инструкция — фрейм howto в теле страницы: он выводит шаги и печатает разметку HowTo в своей верстке.
Адрес страницы задает url: сегменты до последнего называют родителей, последний сегмент становится слагом самой страницы, и url: "/demo/game/" публикует страницу по адресу /demo/game/ при любом name. Явное свойство slug главнее последнего сегмента url, а без slug и url эндпоинт собирает слаг из name. Если страницы по адресу из url нет, а под тем же родителем в том же языке есть страница со слагом из name, эндпоинт обновляет ее на месте и в warnings пишет, как перенести ее на адрес из url: добавьте в свойства id этой страницы и slug. Страницу без адреса эндпоинт не записывает и отвечает 400 page_address_empty.
Редиректов у страниц нет. Старый адрес и переезд раздела заводятся правилом сайта — Редиректы; ключ redirect в свойствах публикация не записывает и дает предупреждение. Сменили у опубликованной страницы slug или родителя — правило 301 со старого адреса заводится само, для неё и для каждого опубликованного потомка. Пробного прогона у публикации нет: посмотреть результат до выхода можно, опубликовав страницу со status: draft.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
markdown_required | 400 | нет markdown или не строка | пришлите документ строкой |
blocks_invalid | 400 | blocks не массив | — |
dry_run_not_supported | 400 | передан dry_run | опубликуйте черновиком: status: draft |
page_property_in_body | 400 | свойство страницы (url, name, lang, status, seo_title и другие) пришло полем тела рядом с markdown; fields | перенесите его в свойства в начале markdown |
unknown_body_field | 400 | в теле поле, которого нет в таблице выше; fields, accepted | уберите поле |
url_invalid | 400 | последний сегмент url не дает адреса, например /docs/!!!/; segment | латиница, цифры и -: /docs/game/ |
page_address_empty | 400 | у страницы не выходит адреса: нет ни slug, ни url, ни name | задайте url или slug в свойствах |
invalid_status | 400 | статус вне publish, draft, pending, private, future | — |
invalid_date | 400 | date — не дата; field, value | "2026-10-05 09:00" или ISO 8601 |
frame_single | 400 | во frame список | один слаг или id |
image_invalid | 400 | image не номер и не адрес картинки; field | — |
block_unsupported | 400 | тип блока в blocks вне разрешённых | — |
image_not_found | 404 | вложения с таким номером нет | загрузите картинку POST /media |
frame_not_found | 404 | фрейма нет на сайте | проверьте слаг в GET /frames |
page_changed | 409 | страница новее if_modified; modified — текущая версия | перечитайте страницу и повторите |
page_not_matched | 409 | if_modified передан, а страницы для обновления нет | уберите if_modified или укажите id |
ambiguous_page | 409 | слаг у нескольких страниц; matches | укажите id или полный url |
lang_mismatch | 409 | id — страница другого языка | поправьте lang или id |
domain_mismatch | 409 | свойство domain — другой сайт | уберите domain |
translation_group_conflict, front_page_group_conflict | 409 | найденная страница состоит в другой группе переводов | выведите её из группы DELETE /pages/{ref}/translations |
translation_link_failed | 422 или 409 | translation_of не сработал; reason | проверьте оригинал и язык |
image_not_image | 422 | вложение не картинка | — |
parent_not_found | 422 | сегмент url — не страница; segment. Родителей API не создает | опубликуйте сначала родителя |
absolute_url_in_path | 422 | схема или домен в url | только путь: /docs/start/ |
unknown_language | 422 | языка lang нет на сайте; languages | добавьте язык PATCH /data/site.languages |
page_vanished | 500 | страница исчезла во время записи | повторите запрос |
image_upload_failed | 502 | картинка по адресу не скачалась или не залилась | проверьте адрес |
post_type_not_supported | 501 | свойство type не page | — |
acf_block_unsupported | 501 | в blocks блок стороннего конструктора | замените его блоком core/* или onpress/* |
Отложенная публикация
Страница выходит в назначенное время сама: дата впереди и статус future (или publish — с датой впереди он тоже становится future). В пределах минуты после даты страница публикуется так же, как при обычном сохранении: появляется по адресу, в лентах и списках дочерних страниц, в карте сайта, в markdown-копии и llms.txt. Перенос даты переносит выход, сохранение со status: draft снимает планирование. Запланировать можно одну страницу — свойство date, серию — POST /pages/series, перевод — полем publish_at в POST /pages/import. Например:
---
name: "Анонс релиза"
url: "/news/release/"
status: "future"
date: "2026-10-05 09:00"
---
Запланированная страница находится по слагу как любая другая, повторная публикация обновит её, а не создаст дубль; в списке страниц она видна со status=future, а что назначено — в поле scheduled ответа.
PATCH /pages/{ref}/markdown
Меняет кусок текста страницы, не пересылая её целиком. Правки применяются по очереди к свежей выгрузке на сервере и записываются одной записью, как у POST /pages/markdown; одна не прошла — не записывается ничего. Правки одной страницы идут по очереди, а если страницу изменили другим эндпоинтом между чтением и записью, правка повторяется на новом тексте. Например, заменить фразу и добавить раздел:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
patch | array | — | обязательно; список правок, у каждой action; одну правку можно прислать прямо в теле |
lang | string | — | язык страницы для поиска по слагу; можно в строке запроса |
if_modified | string | — | версия, которую вы читали; страница новее — 409 page_changed, без повтора |
dry_run | bool | false | вернуть итоговый документ полем markdown, ничего не записывая |
action | Поля | Что делает |
|---|---|---|
replace | find, with, count | заменяет точный текст find в теле на with ("" — удалить); count — сколько вхождений ждём: 1 по умолчанию, число или "all" |
replace_section | heading, markdown | заменяет раздел — от заголовка до следующего заголовка того же или более высокого уровня; заголовок пишется в markdown |
insert_after_section | heading, markdown | вставляет markdown после раздела с подразделами |
insert_before_section | heading, markdown | вставляет markdown перед заголовком раздела |
remove_section | heading | убирает раздел с подразделами |
set_frontmatter | fields | ставит свойства страницы: {"seo_title": "…"}; null убирает ключ; id и data здесь не меняются |
Раздел ищется по тексту заголовка: "## Зачем они нужны" — на этом уровне, "Зачем они нужны" — на любом; заголовки в блоках кода не считаются. Разделов с таким заголовком несколько — назовите уровень или выберите нужный полем "occurrence": 2.
curl -s -X PATCH "$API/site/$SITE/pages/proba-api/markdown" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"patch": [{"action": "replace", "find": "Старое слово.", "with": "Новое слово."}, {"action": "insert_after_section", "heading": "## Раздел", "markdown": "## Второй раздел\n\nЕщё текст."}]}'
{
"success": true,
"id": 6484,
"lang": "ru",
"applied": [
{ "action": "replace", "replaced": 1 },
{ "action": "insert_after_section", "heading": "## Раздел", "line": 5, "lines": 3 }
],
"changed": true,
"written": true,
"previous_modified": "2026-10-27 11:25:00",
"modified": "2026-10-27 11:25:05",
"status": "publish",
"url": "/proba-api/",
"warnings": [],
"cache": { "status": "flushed", "urls": ["https://example.com/proba-api/"] },
"rebaked": { "status": "nothing", "reason": "only the body changed: …" }
}
applied — по записи на правку: replaced у замены, heading, line и lines у разделов, set и removed у свойств. modified — версия после записи, её передают следующей правкой как if_modified. changed: false — правки не меняют текст, запись не идёт. data_keys и data_keys_note приходят, как в POST /pages/markdown, когда запись сохранила свои ключи страницы. rebaked — пересборка фреймов, которые выводят заголовки, адреса и выдержки страниц: она идёт, только если менялись свойства.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
nothing_to_update | 400 | нет patch; actions | пришлите список правок |
bad_patch_op | 400 | правка без нужного поля или с неизвестным action; index | проверьте правку по таблице действий |
frontmatter_field_locked | 400 | set_frontmatter меняет id или data | data — через PATCH /pages/{ref} |
text_not_found | 404 | find нет в теле; in_frontmatter: true, если текст в свойствах | перечитайте текст или используйте set_frontmatter |
section_not_found | 404 | раздела нет; headings — заголовки страницы | возьмите заголовок из headings |
text_count_mismatch | 409 | вхождений не столько, сколько ждали; found, expected | удлините find или передайте count |
section_ambiguous | 409 | разделов с таким заголовком несколько; matches | назовите уровень или occurrence |
page_changed | 409 | страница новее if_modified | перечитайте и повторите |
page_not_found | 404 | страницы нет | проверьте {ref} |
page_language_mismatch | 409 | id или путь называют страницу другого языка; actual_lang | поправьте lang |
PUT /data/pages.content
Тело страницы — это ещё и системные данные pages.content: читаются GET /data/pages.content?page={ref} и пишутся PUT /data/pages.content той же записью, что у POST /pages/markdown. Чтение отдаёт один элемент {id, lang, modified, frame, markdown, body}, и этот ответ можно прислать обратно как есть. Если в markdown только текст без свойств, эндпоинт берет свойства из текущей выгрузки страницы и меняет только тело. Например, заменить текст страницы proba-api:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
page | number, string | — | обязательно; id, слаг или путь страницы |
markdown | string | — | обязательно; документ со свойствами или только текст |
if_modified | string | — | modified из чтения; страница новее — 409 page_changed |
lang, frame, faq_autodetect | — | — | как у POST /pages/markdown |
dry_run | bool | false | разобрать документ и сверить версию без записи |
curl -s -X PUT "$API/site/$SITE/data/pages.content" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"page": "proba-api", "markdown": "Новый текст страницы.", "if_modified": "2026-10-27 11:25:05"}'
Ответ — ответ POST /pages/markdown плюс mode (document или body), modified — версия после записи и rebaked. С dry_run — {dry_run: true, applied: false, id, lang, mode, modified, markdown}.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_page | 400 | нет page | назовите страницу |
markdown_required | 400 | нет markdown | — |
page_not_matched | 409 | id в свойствах не совпадает с page | уберите id из свойств |
page_changed | 409 | страница новее if_modified | перечитайте и повторите |
page_not_found | 404 | страницы нет | — |
Остальные отказы — как у POST /pages/markdown. Новая страница этим путём не создаётся, удаляется страница DELETE /pages/{ref}.
POST /pages/series
Публикует несколько страниц одним вызовом с выходом по одной: первая — в publish_at, каждая следующая — через interval. Каждая страница пишется так же, как POST /pages/markdown, серия только назначает даты. Например, две страницы с шагом две минуты:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
pages | array | — | обязательно; от 1 до 50 страниц: {"markdown": "…", "frame": "…", "blocks": [...]} или строка markdown |
publish_at | string, number | — | дата первой страницы: время сайта "2026-10-05 09:00", ISO 8601 с Z или смещением, unix-секунды |
interval | string, number | — | шаг: секунды (3600) или "90s", "15m", "1h", "1d", "1h30m"; от 60 секунд до 30 дней. Без publish_at первая страница выходит сразу |
dry_run | bool | false | проверить и показать даты без записи |
curl -s -X POST "$API/site/$SITE/pages/series" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"publish_at": "2026-10-02T19:07:00Z", "interval": "2m", "pages": ["---\nname: Серия 1\nurl: /docs/series-1/\n---\nТекст", "---\nname: Серия 2\nurl: /docs/series-2/\n---\nТекст"]}'
{
"success": true,
"applied": true,
"site_id": "SITE_ID",
"now": "2026-10-02T19:04:55Z",
"schedule": { "publish_at": "2026-10-02T19:07:00Z", "interval_seconds": 120, "last_at": "2026-10-02T19:09:00Z", "timezone": "UTC-5" },
"items": [
{ "index": 0, "result": "written", "id": 7064, "updated": false, "title": "Серия 1", "status": "future", "date": "2026-10-02 14:07:00", "date_gmt": "2026-10-02T19:07:00Z", "scheduled": { "at": "2026-10-02T19:07:00Z", "date": "2026-10-02 14:07:00", "event": true }, "url": "/docs/series-1/", "link": null, "warnings": ["…"] },
{ "index": 1, "result": "written", "id": 7065, "status": "future", "date_gmt": "2026-10-02T19:09:00Z", "…": "…" }
],
"warnings": []
}
Страница номер N (с нуля) получает publish_at + N × interval и статус future; прошедшая дата публикует страницу сразу, эндпоинт заменяет status и date из свойств с предупреждением. Без publish_at и interval эндпоинт пишет страницы так, как задают их свойства. Сначала проверяется вся серия, ошибка хоть в одной странице — 422 series_invalid без записи. Если запись всё же упала посреди серии, ответ — success: false, error: "series_partial", failed; записанные до неё остаются, остальные — not_written. items[].result — written, failed или not_written; schedule — null без расписания.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
pages_required | 400 | нет pages или список пуст | — |
invalid_date | 400 | publish_at не дата | — |
invalid_interval | 400 или 422 | interval не шаг (400) или вне 60 с … 30 дней (422; seconds, min, max) | — |
series_too_large | 413 | больше 50 страниц | разбейте серию |
series_invalid | 422 | ошибки в страницах: errors: [{index, code, message}], в том числе duplicate_slug | исправьте страницы, ничего не записано |
POST /pages/import
Записывает перевод страницы одним запросом: тело — ответ GET /pages/{ref}/export, переведённый на целевой язык. Страница пишется как POST /pages/markdown и сразу встаёт в группу переводов оригинала, каждый набор из datasets и shared — как PUT /data/{name} с lang, меню — как PUT /data/menu.{область} (пункт-страница указывает на перевод этой страницы, если он есть). Сначала проверяется всё, и при любой ошибке не пишется ничего. Набор, оставшийся на языке оригинала, не записывается (untouched). Например, перевод на английский:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
markdown | string | — | переведённый документ; в свойствах translation_of оригинала и целевой lang, без id; null — только данные |
lang | string | — | целевой язык; по умолчанию lang из свойств |
datasets, shared | object | — | разделы выгрузки с переведёнными items; до 80 наборов вместе |
page | object | — | раздел page выгрузки; из него берётся оригинал, если в свойствах нет translation_of |
publish_at | string | — | запланировать выход перевода, как у серии |
if_modified | string | — | версия уже существующего перевода |
dry_run | bool | false | проверить и показать план без записи |
curl -s -X POST "$API/site/$SITE/pages/import" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"lang": "en", "markdown": "---\ntranslation_of: 7036\nlang: en\nname: \"Home\"\nslug: \"home\"\n---\n\nText.", "datasets": {"home_stats": {"items": [{"value": "1.2M+", "label": "drafts written last year"}]}}}'
Ответ — lang, source ({id, lang} оригинала), results — по записи на страницу, набор и меню со status (written, unchanged, untouched, failed, not_written, в пробном прогоне — would_write), page — ответ записи страницы, rebaked и warnings. Если запись упала после чистой проверки, ответ — success: false, error: "import_partial", failed; записанное до неё остаётся.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
import_body_required | 400 | тело не объект | пришлите выгрузку |
markdown_invalid | 400 | markdown не строка и не null | — |
lang_required | 400 | целевой язык не назван | добавьте lang в тело или в свойства |
unknown_language | 422 | языка нет на сайте; languages | добавьте язык PATCH /data/site.languages |
polylang_missing | 409 | у сайта нет языков | добавьте язык |
import_same_language | 409 | оригинал уже на целевом языке | правьте его POST /pages/markdown |
import_too_large | 413 | больше 80 наборов | разбейте импорт |
import_invalid | 422 | ошибки в записях: errors: [{target, code, message}] — translation_of_required, source_page_not_found, id_in_translation, lang_mismatch, page_changed, page_not_matched, duplicate_entry и ошибки наборов | исправьте записи, ничего не записано |
GET /pages
Возвращает список страниц сайта по страницам выдачи. Например, страницы верхнего уровня на английском:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | все языки | слаг языка или all; ответ всегда называет язык в lang |
status | string | publish,draft,pending,private,future | статусы через запятую или any (с корзиной) |
parent | string | — | id, слаг или путь родителя; 0 — верхний уровень; any — без фильтра |
frame | string | — | слаг или id фрейма; none — страницы без фрейма |
slug | string | — | точный слаг |
search | string | — | подстрока заголовка или слага; по тексту ищет GET /pages/search |
page | number | 1 | страница выдачи |
per_page | number | 50 | до 200 |
orderby | string | menu_order | menu_order, title, date, modified, id, slug |
order | string | asc | asc, desc |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages?lang=en&parent=0&per_page=1"
{
"success": true,
"site_id": "SITE_ID",
"lang": "en",
"lang_filtered": true,
"languages": ["en", "ru"],
"default_lang": "en",
"statuses": ["publish", "draft", "pending", "private", "future"],
"filters": { "parent": 0 },
"total": 34,
"page": 1,
"per_page": 1,
"total_pages": 34,
"orderby": "menu_order",
"order": "asc",
"items": [
{
"id": 6183, "title": "Documentation", "slug": "docs", "status": "publish", "lang": "en",
"url": "/docs/", "link": "https://example.com/docs/", "parent": 0, "menu_order": 0,
"children_count": 17, "is_front": false, "date": "2026-09-12 12:48:11", "modified": "2026-09-21 16:20:24",
"frame": { "id": "af33214f168f8efd", "slug": "docs", "meta_key": "_onpress_frame" },
"topic": [], "seo": { "title": "…", "description": "…", "keyword": "" }, "target": "", "anchor": "",
"static": { "published": false, "url": null, "app_key": "page-6183" }
}
]
}
Счётчик страниц выдачи — total_pages. url — с префиксом языка, даты — время сайта в виде ГГГГ-ММ-ДД ЧЧ:ММ:СС, modified — версия для if_modified. static — есть ли статическая копия страницы.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
unknown_language | 422 | языка нет; languages | — |
unknown_status, unknown_orderby | 422 | значение вне списка | — |
frame_not_found | 404 | фрейма из frame нет | — |
page_not_found, ambiguous_page | 404, 409 | родитель из parent не найден или неоднозначен | назовите родителя id или путём |
polylang_inactive | 409 | lang на сайте без языков | уберите lang |
GET /pages/search
Ищет слово, ссылку или шаблон в тексте всех страниц сайта. Ищется тот же markdown, что отдаёт GET /pages/{ref}/markdown: номер строки в ответе совпадает с номером строки выгрузки, и найденное место правят PATCH /pages/{ref}/markdown. Например, где в доках упоминается menu.header:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
q | string | — | обязательно; что искать, до 500 символов |
regex | bool | 0 | 1 — q это регулярное выражение JavaScript, применяется к каждой строке отдельно |
case | bool | 0 | 1 — с учётом регистра |
in | string | body | body — текст после свойств, title — заголовок, frontmatter — все строки свойств страницы, seo — seo_title, seo_description, focus_keyword, all — всё; можно через запятую |
lang | string | все языки | слаг языка |
path | string | — | префикс адреса страницы с префиксом языка: /docs/v2/ |
status | string | publish | статусы через запятую или any |
limit | number | 20 | страниц в ответе, до 100 |
offset | number | 0 | сколько страниц пропустить |
context | number | 60 | символов по обе стороны от вхождения, до 300 |
max_matches | number | 20 | вхождений на страницу, до 500; count считает все |
curl -s -G "$API/site/$SITE/pages/search" -H "Authorization: Bearer $OP_TOKEN" \
--data-urlencode "q=menu.header" --data-urlencode "path=/docs/v2/" --data-urlencode "limit=1" --data-urlencode "max_matches=1"
{
"success": true,
"site_id": "ef7QmgyEc",
"q": "menu.header",
"regex": false,
"case": false,
"in": ["body"],
"lang": "all",
"path": "/docs/v2/",
"statuses": ["publish"],
"scanned": 115,
"total": 9,
"total_matches": 24,
"limit": 1,
"offset": 0,
"took_ms": 100,
"exported": { "cached": 115, "converted": 0 },
"items": [
{
"id": 6261, "title": "Данные", "url": "/docs/v2/ru/guides/data/", "link": "https://onpress.pro/docs/v2/ru/guides/data/",
"lang": "ru", "status": "publish", "count": 8, "truncated": true,
"matches": [ { "in": "body", "line": 20, "column": 215, "match": "menu.header", "text": "…только имя: `menu.header`, `site.languages`…", "section": "Данные" } ]
}
]
}
scanned — сколько страниц просмотрено, total — сколько с вхождениями, total_matches — вхождений всего; страницы идут по убыванию count, затем по url. У вхождения: in, line (с 1, у title — null), column, match, text — строка, обрезанная до context символов, section — ближайший заголовок выше. exported — сколько выгрузок взято из памяти сервиса и сколько сделано заново; страница, которую не удалось выгрузить, попадает в errors. Страницу со слагом search этим адресом не прочитать — запрашивайте её по id или пути.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
q_required, q_too_long | 400 | нет q или длиннее 500 символов | — |
regex_invalid | 400 | выражение не разбирается | — |
regex_timeout | 422 | выражение выполнялось дольше 2 с | упростите его или сузьте path |
unknown_field, unknown_language, unknown_status | 422 | значение вне списка | — |
too_many_pages | 422 | под поиск попало больше 3000 страниц | сузьте path, lang или status |
GET /pages/{ref}
Возвращает одну страницу: паспорт, свойства, фрейм, группу переводов, мету и тело в markdown. Например, русская страница proba-api без тела:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | — | в каком языке искать слаг и с чем сверить язык |
markdown | bool | true | false — не выгружать тело |
as | string | — | translation_source — свойства выгрузки с translation_of вместо id и без status |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/proba-api?lang=ru&markdown=false"
Ответ — lang, languages, default_lang, объект page и рядом markdown. В page — поля элемента списка, в том числе modified, и ещё:
Поле page | Смысл |
|---|---|
excerpt | выдержка |
thumbnail | превью {id, url, width, height, alt} или null |
path | путь без префикса языка |
parents | цепочка родителей от корня [{id, title, slug, status}] |
translations | группа переводов |
frontmatter | свойства страницы в том виде, в каком их пишет публикация |
meta | значимая мета страницы |
edit_url, view_url, preview_url | адреса в консоли и на сайте |
Если выгрузка тела не удалась, страница всё равно возвращается: markdown: null и markdown_error: {code, message}.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
page_not_found | 404 | страницы нет | проверьте {ref} |
not_a_page | 404 | id называет не страницу | — |
ambiguous_page | 409 | слаг у нескольких страниц; candidates | назовите страницу id или путём |
page_language_mismatch | 409 | страница на другом языке; actual_lang | поправьте lang |
unknown_language | 422 | языка нет на сайте | — |
polylang_inactive | 409 | lang на сайте без языков | уберите lang |
GET /pages/{ref}/markdown
Возвращает только тело — ровно в том формате, который принимает публикация. Параметры lang и as — как у GET /pages/{ref}. Вызовы фреймов выгружаются короткой формой <!-- frame/<имя> {параметры} --> … <!-- /frame -->, какой бы формой их ни записали. Повторная публикация выгрузки тело не меняет. Например:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/ru~proba-api/markdown"
{
"success": true,
"site_id": "SITE_ID",
"lang": "ru",
"id": 6431,
"modified": "2026-10-27 11:25:05",
"frame": { "id": "e627bf4dbefcc374", "slug": "docs-proba", "meta_key": "_onpress_frame" },
"markdown": "---\nid: 6431\nname: \"Проба API по-русски\"\nslug: \"proba-api\"\nstatus: \"publish\"\nurl: \"/proba-api/\"\nlang: \"ru\"\nframe: \"docs-proba\"\n---\n\nРусский текст.",
"meta": { "id": 6431, "name": "Проба API по-русски", "slug": "proba-api", "…": "…" }
}
modified — версия страницы на момент выгрузки: передайте её в if_modified при записи, второй запрос за версией не нужен. Ключевой запрос страницы стоит в свойстве focus_keyword, закрытая от поисковиков страница получает строку noindex: true. Отказы — как у GET /pages/{ref}, плюс 501 exporter_missing (на сайте нет выгрузчика) и export_failed.
GET /pages/{ref}/export
Отдаёт одним ответом всё, что нужно для перевода страницы: паспорт, тело в markdown, наборы данных, которые читают фрейм страницы и вложенные фреймы, и отдельно — общее для сайта: шапку, подвал, меню. Ответ можно целиком отдать нейросети с заданием «переведи на язык X» и залить результат POST /pages/import; инструкция — в поле howto самого ответа. Например:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/home/export?lang=en"
{
"success": true,
"kind": "onpress-page-export",
"version": 1,
"site_id": "SITE_ID",
"howto": "Translate this page from \"en\" into the target language and return a result in the same shape.\n…",
"page": { "id": 7036, "slug": "home", "lang": "en", "title": "Home", "status": "publish", "url": "/home/", "frame": { "id": "4de31ed24a7771c3", "slug": "home" }, "seo": { "title": "", "description": "", "keyword": "" }, "excerpt": "", "frontmatter": { "…": "…" } },
"translations": { "en": 7036 },
"languages": ["en", "ru"],
"markdown": "---\ntranslation_of: 7036\nname: \"Home\"\nslug: \"home\"\n…\n---\n\n…",
"frames": [ { "slug": "home", "id": "4de31ed24a7771c3", "shared": false }, { "slug": "site__header", "id": "…", "shared": true } ],
"datasets": { "home_stats": { "origin": "site", "lang": "en", "variants": [null], "total": 3, "items": [ { "value": "1.2M+", "label": "drafts written last year" } ] } },
"shared": { "note": "Common to the whole site (header, footer, menus), not to this page: translate once per language.", "sources": { "menu.header": { "origin": "system", "lang": "en", "total": 3, "items": [ "…" ] } } },
"computed_sources": []
}
| Поле | Смысл |
|---|---|
kind, version | onpress-page-export и 1 — признак выгрузки страницы |
howto | задание для нейросети на английском: что переводить, что не трогать (id, slug, url, ключи, ссылки, код) и как залить результат |
page | паспорт: id, slug, lang, title, status, url, link, parent, parents, frame, seo, excerpt, frontmatter |
markdown | тело в виде ?as=translation_source: translation_of вместо id, без status |
frames | фреймы страницы, сначала собственный; shared: true — фрейм шапки или подвала |
datasets | наборы страницы на её языке: origin, lang, variants (языки, на которых набор хранится; null — общий список), total, items, lang_fallback |
shared | {note, sources} — общие для сайта источники menu.*, site.*, наборы шапки и подвала; переводятся один раз на язык |
computed_sources | pages.* и breadcrumbs — собираются из заголовков страниц, отдельно не переводятся |
unresolved_sources, missing_frames | только если есть: источники и вложенные фреймы, которых на сайте нет |
Текст, вписанный прямо в разметку фрейма, а не лежащий в наборе, в выгрузку не попадает — его переводят правкой фрейма. Отказы — как у GET /pages/{ref}.
GET /pages/{ref}/related
Возвращает кандидатов для внутренних ссылок со страницы: опубликованные страницы её языка, своя тема (topic) впереди. Сама страница, её переводы, главные, служебные и закрытые от индексации страницы в выдачу не попадают. Например, 5 кандидатов:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | — | язык, в котором искать слаг страницы |
limit | number | 20 | до 50 |
topic | string | — | темы через запятую вместо тем страницы |
exclude | string | — | id или адреса через запятую, которые не предлагать |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/6285/related?limit=5"
Ответ — page (id, title, url, topic, keyword), lang, topic, total и items: у кандидата id, title, url, path, topic, keyword, description, same_topic, score и reason — почему он выше соседа.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
page_not_found | 404 | страницы нет | — |
page_has_no_language | 409 | у страницы нет языка | проставьте его POST /pages/language |
page_language_mismatch | 409 | страница на другом языке | поправьте lang |
PATCH /pages/{ref}
Меняет поля страницы, не трогая тело: выдержку, фрейм, превью, пошаговую разметку и свои поля. Пробного прогона нет. Например, задать выдержку и снять фрейм:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
excerpt | string | — | новая выдержка; "" или null очищают |
frame | string | — | слаг или id фрейма; null, false, "", "none" снимают |
thumbnail | number, string | — | превью: номер вложения или адрес картинки (чужая заливается в медиатеку), {"id": …} или {"url": …}; null, 0, "" снимают. То же, что свойство image |
data | object | — | свои поля, которые фрейм читает токеном page.data.<ключ>: {"ключ": "значение"}; "" или null снимают |
if_modified | string | — | версия, которую вы читали; страница новее — 409 page_changed |
lang | string | — | язык для поиска слага; можно в строке запроса |
republish | bool | true | false — не пересобирать статическую копию |
curl -s -X PATCH "$API/site/$SITE/pages/ru~proba-api" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"excerpt": "Выдержка ru", "frame": null}'
{
"success": true,
"site_id": "SITE_ID",
"lang": "ru",
"page": { "id": 6431, "title": "Проба API по-русски", "slug": "proba-api", "status": "publish", "lang": "ru", "url": "/ru/proba-api/", "excerpt": "Выдержка ru", "frame": null, "…": "…" },
"changed": ["excerpt", "frame"],
"excerpt": { "from": "", "to": "Выдержка ru" },
"frame": { "from": { "id": "e627bf4dbefcc374", "slug": "docs-proba", "meta_key": "_onpress_frame" }, "to": null },
"static": { "published": false, "url": "", "reason": "no_frame", "was_published": false, "removed": false, "changed": false },
"republish": { "status": "ok", "…": "…" },
"cache": { "status": "flushed", "urls": ["https://example.com/ru/proba-api/"] }
}
changed — что реально изменилось; ничего — перепубликации нет (republish.status: "skipped", reason: "nothing_changed"). static.reason — renders_on_request, no_frame, not_public, bad_path или пусто, если копия на месте; republish.status — ok, skipped, unavailable, failed, неудача не отменяет правку. У превью в ответе page.thumbnail и thumbnail: {from, to}, эндпоинт отвечает, когда og:image на странице уже новый. Свои поля приходят как data: {ключ: {from, to}}; ключи с подчеркиванием и свойства со своей обработкой (title, slug, frame, excerpt, image) сюда не принимаются, значение — строка, число или булево. Какие ключи читает фрейм страницы — GET /pages/{ref}/data-fields.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
nothing_to_update | 400 | нет ни одного поля; fields | — |
dry_run_not_supported | 400 | передан dry_run | прочитайте страницу GET /pages/{ref} |
redirect_field_removed | 400 | прислано redirect | заведите правило: Редиректы |
data_invalid, data_key_invalid, data_key_reserved, data_value_invalid | 400 | data не объект, ключ не по правилам или занят, значение — список или объект; key | — |
excerpt_invalid, frame_single | 400 | значение не того вида | — |
image_invalid | 400 | превью не номер и не адрес; field: "thumbnail" | — |
image_not_found | 404 | вложения нет | — |
image_not_image | 422 | вложение не картинка | — |
image_upload_failed | 502 | картинка по адресу не залилась | — |
frame_not_found | 404 | фрейма нет | — |
page_changed | 409 | страница новее if_modified | перечитайте и повторите |
page_not_found, ambiguous_page, page_language_mismatch | 404, 409 | как у GET /pages/{ref} | — |
unknown_language | 422 | языка нет на сайте | — |
GET /pages/{ref}/data-fields
Возвращает, что фрейм страницы вместе с вложенными фреймами берёт из данных именно для этой страницы, какого типа каждое поле и куда записать правку; на этом построен блок «Данные страницы» в консоли. Данные вызовов фреймов в теле страницы сюда не входят — они в markdown. Например:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/maya-collins/data-fields"
{
"success": true,
"page_id": 7151,
"lang": "en",
"frame": { "id": "3c91ed8471c4f391", "slug": "author__page" },
"frames": [{ "id": "3c91ed8471c4f391", "slug": "author__page" }, { "…": "…" }],
"page_data": [
{ "key": "cta", "value": "0", "type": "string", "in_frontmatter": true, "written": "cta", "role": "selector", "uses": [{ "frame": "site__cta", "as": "match" }], "write": { "method": "PATCH", "path": "/pages/7151", "body": { "data": { "cta": "<value>" } } } }
],
"items": [
{
"dataset": "author", "lang": null, "index": 0, "total": 1, "ref": { "slug": "default" }, "found": true,
"fields": [ { "key": "role", "value": "Head of Product, Acme", "type": "string", "used": true, "uses": [{ "frame": "author__page", "as": "text" }] } ],
"write": { "method": "PATCH", "path": "/data/author", "body": { "patch": [{ "action": "merge", "match": { "slug": "default" }, "fields": {} }] } }
}
],
"shared": [ { "source": "menu.header", "origin": "system", "contextual": false, "frames": ["site__header"] } ]
}
| Раздел | Что это | Чем писать |
|---|---|---|
page_data | свои поля страницы: токены page.data.<ключ> | PATCH /pages/{ref} с {"data": {…}} |
items | элемент набора, который страница выбирает повторителем с match по полю-идентификатору (slug, id, key, page, url, path, name) | PATCH /data/{набор} с merge |
shared | данные, общие для сайта, и списки страниц | PUT или PATCH /data/{имя} |
У поля type — тип редактора: string, url, text, image, number, boolean, list, object; used и uses — выводит ли поле фрейм и где. Поле, по которому страница выбирает элемент, приходит с readonly: true. Нет элемента для страницы — found: false, missing_note и write: null; добавьте его PATCH /data/{набор} с append.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
page_not_found, ambiguous_page, page_language_mismatch | 404, 409 | как у GET /pages/{ref} | — |
unknown_language | 422 | языка нет на сайте | — |
internal_unavailable | 502 | сайт не ответил на разбор | повторите запрос |
DELETE /pages/{ref}
Кладёт страницу в корзину, а с force удаляет безвозвратно; снимает статическую копию и сбрасывает кэш. У опубликованной страницы адрес её и опубликованных потомков уводится правилом 301 на ближайшего опубликованного родителя или главную языка — поля redirects_created и auto_redirects. Например, посмотреть план удаления страницы с детьми:
| Поле или параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
force | bool | false | удалить безвозвратно вместо корзины |
with_children | bool | false | удалить всё поддерево снизу вверх |
lang | string | — | язык для сверки |
dry_run | bool | false | показать план без удаления |
curl -s -X DELETE "$API/site/$SITE/pages/6428?with_children=1&dry_run=1" -H "Authorization: Bearer $OP_TOKEN"
{
"success": true,
"site_id": "SITE_ID",
"lang": "en",
"dry_run": true,
"mode": "trash",
"page": { "id": 6428, "title": "Проба API", "slug": "proba-api", "lang": "en" },
"targets": [
{ "id": 6429, "title": "Дочерняя проба", "status": "draft", "url": "/?page_id=6429", "…": "…" },
{ "id": 6428, "title": "Проба API", "status": "publish", "url": "/proba-api/", "translations": { "ru": 6431 }, "…": "…" }
],
"translations": { "en": 6428, "ru": 6431 }
}
Настоящий вызов отвечает mode (trash или delete), page со status_before, removed — по записи на страницу (removed, static, translations_unlinked, translations_kept), translations: {before, after}, cache и restore — как найти страницу в корзине (null после force). Отказ удаления одной страницы поддерева приходит в её записи (removed: false, error: "delete_failed").
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
page_has_children | 409 | у страницы есть дети; children | передайте with_children: true |
front_page_delete | 409 | главная, страница записей или главная языка | сначала назначьте другую главную PUT /data/site.front_page |
page_has_no_language | 409 | передан lang, а у страницы языка нет | уберите lang |
page_not_found, ambiguous_page, page_language_mismatch | 404, 409 | как у GET /pages/{ref} | — |
unknown_language | 422 | языка нет на сайте | — |
internal_unavailable | 502 | сайт не ответил | повторите запрос |
POST /pages/language
Проставляет страницам язык: названным в pages или всем страницам без языка. Всё идёт одной транзакцией, правило редиректа смена языка не заводит. Например, найти страницы без языка:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | основной язык | какой язык поставить |
pages | array | — | id, слаги или пути страниц |
missing | bool | false | true — взять все страницы без языка |
statuses | string | publish | статусы для missing через запятую |
dry_run | bool | false | показать и не писать |
curl -s -X POST "$API/site/$SITE/pages/language" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"missing": true, "dry_run": true}'
{ "success": true, "site_id": "SITE_ID", "lang": "en", "dry_run": true, "total": 0, "changed": 0, "pages": [] }
Каждая страница в pages — {id, title, url, lang: {from, to}, changed}.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
nothing_to_update | 400 | нет ни pages, ни missing | — |
polylang_inactive | 409 | у сайта нет языков | добавьте язык PATCH /data/site.languages |
unknown_language | 422 | языка нет; languages | — |
page_not_found | 404 | страницы из pages нет | — |
POST /sections/{section}
Заводит корень раздела — страницу /news/, /changelog/ или /docs/ с фреймом и наборами ленты, как на демо-сайте. Недостающие фреймы ставятся на сайт из библиотеки вместе с вложенными, пустые наборы ленты заводятся, если их нет; страница публикуется сразу. Записи раздела — её дочерние страницы. Например, раздел новостей:
{section} | Фрейм страницы | Тело страницы | Ещё ставится |
|---|---|---|---|
news | landing--news | шапка news__masthead, лента news__feed | фреймы news__post, news__category; наборы news_posts, news_cats, news_more, news_more_cat, pager |
changelog | landing--changelog | шапка changelog__hero, лента changelog__feed | фрейм changelog__post; наборы changelog на языке страницы и pager |
docs | docs | пустое | — |
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | основной язык | язык страницы; заголовок — «Новости», «Чейнджлог», «Документация» для ru, иначе News, Changelog, Documentation |
curl -s -X POST "$API/site/$SITE/sections/news" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' -d '{}'
{
"success": true,
"section": "news",
"lang": "en",
"page": { "id": 7210, "title": "News", "slug": "news", "url": "/news/", "link": "https://example.com/news/", "frame": "landing--news" },
"frames_installed": [ { "slug": "landing--news", "status": "installed", "bake": "ok" }, { "slug": "news__masthead", "status": "installed", "bake": "ok" } ],
"datasets_created": ["news_posts", "news_cats", "news_more", "news_more_cat", "pager"]
}
frames_installed — какие фреймы встали на сайт (уже стоявшие не перечисляются), datasets_created — какие наборы заведены, набор на языке — с суффиксом (changelog__en).
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_section | 400 | {section} не news, changelog или docs; allowed | — |
invalid_lang | 400 | у сайта нет такого языка; languages | — |
section_exists | 409 | корень раздела на этом языке уже есть; page | правьте его POST /pages/markdown |
system_frame_not_found | 404 | нужного фрейма нет в библиотеке | — |
system_frame_missing | 409 | версия фрейма в библиотеке без разметки | повторите позже |
Ссылки страниц
Какие ссылки стоят в тексте страниц, куда ведут и с каким анкором — для проверки перелинковки. Ссылки берутся только из тела страницы: текст, markdown внутри вызовов фреймов и данные блоков; шапка, подвал, меню, крошки и оглавление не учитываются. Ссылки собираются по запросу и хранятся: чтение отдаёт последнюю сборку. Страница, записанная или удалённая через API, пересобирается сама через пару секунд; правка мимо API, новые редиректы, правка фреймов или наборов карточек видны в ответе как stale: true до следующей сборки.
POST /links/build
Разбирает все страницы сайта, кроме корзины, и сохраняет результат; сайт в пару сотен страниц собирается примерно за секунду. Например:
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/links/build"
{ "success": true, "site_id": "SITE_ID", "building": false, "joined": false, "built": true, "built_at": "2026-10-05T13:29:53.199Z", "duration_ms": 210, "pages": 102, "links": 460, "probed": 0 }
У сайта идёт одна сборка за раз: повторный запрос ждёт текущую и отвечает её итогом с joined: true, а если сборку ведёт другой экземпляр сервиса — сразу 202 с building: true. links — сколько ссылок записано, probed — сколько внутренних адресов пришлось проверить запросом к сайту.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
too_many_pages | 413 | больше 5000 опубликованных страниц | — |
links_build_failed | 500 | сборка упала, прежняя осталась | повторите запрос |
GET /links
Возвращает страницы сайта из последней сборки пачками, у каждой — её ссылки по порядку в тексте. Например, первая страница с внутренними ссылками:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
lang | string | все языки | слаг языка или all |
offset | number | 0 | сдвиг по списку страниц |
limit | number | 100 | до 500 страниц |
kind | string | — | internal или external |
status | string | — | через запятую: ok, broken, redirect, unknown |
nofollow | bool | — | 1 — только ссылки с rel="nofollow" |
q | string | — | подстрока анкора, адреса или конечного адреса редиректа |
cards | bool | 1 | учитывать карточки страниц из блоков фреймов; 0 — без них |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/links?kind=internal&limit=1"
{
"success": true,
"site_id": "SITE_ID",
"built": true,
"built_at": "2026-10-05T13:29:53.199Z",
"stale": false,
"changes": { "pages": 0, "rules": false, "cards": 0, "site": false },
"building": false,
"lang": "all",
"scope": "body",
"external_check": null,
"summary": { "pages": 62, "links": 231, "internal": 231, "external": 0, "cards": 0, "broken": 0, "redirect": 0, "nofollow": 0 },
"total": 28,
"offset": 0,
"limit": 1,
"pages": [
{
"id": 6183, "title": "Documentation", "url": "/docs/", "lang": "en", "parent": 0, "status": "publish", "front": false,
"counts": { "internal": 27, "external": 0, "cards": 0, "broken": 0, "redirect": 0, "nofollow": 0, "inbound": 0, "inbound_pages": 0 },
"links": [
{ "n": 1, "anchor": "Quickstart", "before": "set once in the ", "after": ". Start here", "href": "/docs/bystryy-start/", "url": "https://example.com/docs/bystryy-start/", "kind": "internal", "domain": null, "page": { "id": 6188, "title": "Quickstart", "url": "/docs/bystryy-start/" }, "status": "ok", "final": null, "nofollow": false, "rel": [], "new_tab": false, "image": false, "source": "body", "frame": null }
]
}
]
}
| Поле ссылки | Смысл |
|---|---|
n, anchor | номер по порядку и текст ссылки; у ссылки-картинки — её alt и image: true |
before, after | шесть слов до и после анкора без разметки; склеивайте before + anchor + after как есть |
href, url | адрес как в тексте и полный адрес |
kind, domain | internal — свой сайт; external — чужой, domain без www. |
page | страница сайта, на которую ведёт ссылка, или null |
status | ok; broken — 404 или 410 (code); redirect — конечный адрес в final: {url, page}; unknown; у внешних без проверки — null |
nofollow, rel, new_tab | атрибуты ссылки |
source, frame | body — текст; frame — данные блока фрейма (field — поле адреса); cards — карточка страницы |
counts — числа по странице, inbound и inbound_pages — сколько ссылок и со скольких опубликованных страниц ведёт сюда; summary — те же числа по всем страницам до фильтров; external_check — когда прошла проверка ссылок сайта, по которой отмечены битые внешние. С фильтрами kind, status, nofollow, q в ответе остаются только подходящие страницы и ссылки. Якоря на своей странице, mailto: и tel: ссылками не считаются.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_kind, invalid_status | 400 | значение вне списка | — |
unknown_language | 422 | языка нет | — |
polylang_inactive | 409 | lang на сайте без языков | — |
GET /pages/{ref}/links
Возвращает исходящие ссылки страницы и входящие на неё из последней сборки. Параметры lang, kind, status, nofollow, q, cards — как у GET /links; к входящим применяются все, кроме kind. Например:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/6188/links"
Ответ — page (id, title, url, lang, parent, status, front), counts, links — исходящие в той же форме, что в списке, и inbound — входящие с полем from: {id, title, url, lang}, плюс состояние сборки (built, built_at, stale, changes, building). Страница появилась после сборки — page_built: false и пустые списки.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
page_not_found, ambiguous_page, page_language_mismatch | 404, 409 | как у GET /pages/{ref} | — |
invalid_kind, invalid_status | 400 | значение вне списка | — |
unknown_language | 422 | языка нет | — |
Трафик страниц
Сколько поиска и посетителей приносит каждая страница за последние 12 месяцев: показы и клики в Google из Search Console и посетители из Google Analytics 4 — все и из поиска, по странице входа. Цифры обновляются раз в сутки ночью, если источник подключён у сайта в разделе «Аналитика». Трафик старых адресов с редиректом засчитывается странице, адреса сводятся к пути без протокола, www, параметров и якоря.
GET /traffic
Возвращает опубликованные страницы с трафиком за год пачками. Например, первая страница по показам:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
sort | string | gsc_impressions | gsc_impressions, gsc_clicks, ga_visits, ga_organic, ga_avg_time, ga_bounce_rate, advice, url, title |
order | string | desc у чисел, asc у url и title | asc или desc |
advice | string | — | any или через запятую stale, no_impressions, not_crawled, low_ctr, engagement |
no_impressions | bool | — | 1 — только страницы без показов за год |
lang | string | все языки | слаг языка или all |
status | string | publish | all — все, кроме корзины |
q | string | — | подстрока заголовка или адреса |
offset | number | 0 | сдвиг |
limit | number | 100 | до 500 |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/traffic?limit=1"
{
"success": true,
"site_id": "SITE_ID",
"period": { "days": 365, "from": "2025-10-28", "to": "2026-10-27" },
"updated_at": "2026-10-27T03:30:09.000Z",
"gsc": { "connected": true, "status": "ok", "state": "fresh", "updated_at": "2026-10-27T03:30:09.000Z", "error": null },
"ga": { "connected": true, "status": "ok", "state": "fresh", "updated_at": "2026-10-27T03:30:09.000Z", "error": null },
"unmatched": { "gsc_clicks": 0, "gsc_impressions": 331, "ga_visits": 69, "ga_organic": 4 },
"redirect_paths": 118,
"summary": { "pages": 207, "gsc_clicks": 4911, "gsc_impressions": 115871, "ga_visits": 36277, "ga_organic": 6469, "no_impressions": 50 },
"total": 206,
"offset": 0,
"limit": 1,
"pages": [
{
"id": 7901, "title": "Z.ai (нейросеть GLM): что это и как пользоваться", "url": "/z-ai/", "lang": "ru", "status": "publish", "front": false,
"gsc_clicks": 119, "gsc_impressions": 17979, "ga_visits": 232, "ga_organic": 135, "ga_organic_7d": 4,
"ga_sessions": 262, "ga_engaged_sessions": 142, "ga_engagement_seconds": 13270, "ga_avg_time": 50.6, "ga_bounce_rate": 0.458,
"trend": { "gsc_impressions": -0.19, "gsc_clicks": -0.059, "ga_visits": -0.222, "ga_organic": -0.179, "ga_avg_time": null, "ga_bounce_rate": null }
}
]
}
| Поле | Смысл |
|---|---|
gsc_impressions, gsc_clicks | показы и клики в Google за год; null — Search Console не подключена или данных ещё нет |
ga_visits, ga_organic | пользователи, начавшие визит на странице: все и из поиска; null — Google Analytics не подключён |
ga_organic_7d | пользователи из поиска за последние 7 полных дней |
ga_sessions, ga_engaged_sessions, ga_engagement_seconds | суммы за год по визитам, начавшимся на странице |
ga_avg_time, ga_bounce_rate | среднее время вовлечения на сеанс в секундах и доля отказов от 0 до 1 |
trend | изменение последних 7 полных дней к 7 перед ними, долей; null — мало данных; окна — в trend_windows |
gsc, ga, sources | состояние источника: connected, status (ok, pending, error, denied, off), state, updated_at, error |
unmatched | трафик адресов, которых нет среди страниц и редиректов |
redirect_paths | сколько старых адресов из редиректов учтено |
summary | итог по всем страницам до no_impressions и q |
advice | рекомендации по странице, см. ниже |
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_sort, invalid_order, invalid_status, invalid_advice | 400 | значение вне списка | — |
unknown_language | 422 | языка нет | — |
polylang_inactive | 409 | lang на сайте без языков | — |
Рекомендации
У каждой страницы в ответе GET /traffic есть поле advice — что с ней сделать по уже собранным данным. Новых запросов к Google эндпоинт не делает. У черновиков advice — null.
kind | Когда срабатывает | Важность | Что сделать |
|---|---|---|---|
stale | страница старше 90 дней, за 90 дней ни одного показа; заменяет no_impressions | critical | переработать или удалить с редиректом 301 |
no_impressions | ни одного показа за год, страница старше 14 дней | high | подобрать ключ, раскрыть тему, добавить ссылки, проверить индексацию |
not_crawled | Googlebot не заходил 30 дней или ни разу | high | отправить на индексацию |
low_ctr | показов от 100, CTR ниже 1% | medium | переписать title и description |
engagement | отказы выше 25% или время меньше 30 с при сеансах от 10 | medium | ответить на запрос в первом абзаце, добавить конкретику |
"advice": {
"items": [
{ "kind": "stale", "severity": "critical", "impressions_90d": 0, "days": 90, "age_days": 214, "path": "/google/nano-banana-2/", "done": null, "returned": false }
],
"skipped": [],
"score": 4
}
score — сумма важности (critical 4, high 3, medium 2), skipped — правила, которые не проверялись, и почему: gsc_unavailable, ga_unavailable, too_new, history_short, crawl_unavailable, crawl_window_short, few_sessions, service_page. В summary.advice — сколько страниц с рекомендациями, пороги — в advice_thresholds.
PUT /pages/{ref}/advice/{kind}
Отмечает рекомендацию страницы выполненной, DELETE по тому же адресу снимает отметку. Отметки общие для всех пользователей сайта, в списке у рекомендации появляется done: {at, user_id}. Если отметке больше 30 дней, а рекомендация всё ещё срабатывает, отметка перестаёт действовать: done: null, returned: true, прежняя — в done_before. Например:
curl -s -X PUT -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/z-ai/advice/low_ctr"
{ "success": true, "site_id": "SITE_ID", "page_id": 5776, "kind": "low_ctr", "done": { "at": "2026-10-06T09:02:49.000Z", "user_id": 1 } }
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_advice | 400 | неизвестный kind | — |
page_not_found, ambiguous_page | 404, 409 | как у GET /pages/{ref} | — |
GET /pages/{ref}/traffic
Возвращает трафик одной страницы по периодам — для графиков. Последние 30 дней хранятся по дням, всё старше — по ISO-неделям; свежие дни Search Console дорастают ещё 2–3 ночи. Например, по дням:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
granularity | string | day | day — последние 30 дней; week — недели от первой с данными; month — месяцы из тех же недель |
metrics | string | все | через запятую: gsc_impressions, gsc_clicks, ga_users, ga_organic_users, ga_sessions, ga_engaged_sessions, ga_engagement_seconds, ga_avg_time, ga_bounce_rate |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/z-ai/traffic?granularity=day"
{
"success": true,
"site_id": "SITE_ID",
"page": { "id": 7901, "title": "Z.ai (нейросеть GLM): что это и как пользоваться", "status": "publish" },
"granularity": "day",
"metrics": ["gsc_impressions", "gsc_clicks", "ga_users", "…"],
"from": "2026-09-27",
"to": "2026-10-26",
"points": [
{ "day": "2026-10-26", "gsc_impressions": 189, "gsc_clicks": 1, "ga_users": 1, "ga_organic_users": 0, "ga_sessions": 1, "ga_engaged_sessions": 1, "ga_engagement_seconds": 55, "ga_avg_time": 55, "ga_bounce_rate": 0 }
],
"changes": [ { "date": "2026-10-26", "kind": "edit", "kinds": ["edit"], "note": null } ]
}
Точка недельного ряда — week (понедельник), месячного — month (ГГГГ-ММ); неделя относится к месяцу своего четверга. Пользователи за месяц — сумма более коротких периодов и помечены в approx. Период без трафика приходит нулями. changes — даты правок страницы: published, replaced, address (в note — откуда и куда), edit.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_granularity, invalid_metrics | 400 | значение вне списка | — |
page_not_found, ambiguous_page | 404, 409 | как у GET /pages/{ref} | — |
Сохраненные представления
Свой набор колонок и фильтров раздела «Страницы» можно сохранить под именем как свое представление, в консоли или этими эндпоинтами, и открывать его адресом ?view=<id>. Сохраненные представления личные, у каждого пользователя на каждом сайте свои. Сохраненное представление состоит из базового представления раздела (traffic, seo, default и другие) и параметров адреса, то есть columns и фильтров.
POST /views
Сохраняет представление. GET /views отдаёт {"views": [...]} в порядке сохранения, PATCH /views/{id} меняет name или params, DELETE /views/{id} удаляет. Например:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
name | string | — | обязательно; до 60 символов |
view | string | — | обязательно; базовое представление раздела |
params | object | — | до 30 параметров: ключ — латиница в нижнем регистре, цифры и _, значение — строка до 2000 символов; пустые отбрасываются |
curl -s -X POST "$API/site/$SITE/views" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "Органика", "view": "traffic", "params": {"columns": "page,gscImpressions,gaOrganic", "organic7": "1-"}}'
{ "view": { "id": "v-3f9a1c2b", "name": "Органика", "view": "traffic", "params": { "columns": "page,gscImpressions,gaOrganic", "organic7": "1-" }, "created_at": "2026-10-09T08:12:40.000Z" } }
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
invalid_view_name, invalid_view, invalid_view_params | 400 | имя, представление или параметры не по правилам | — |
nothing_to_update | 400 | в PATCH нет ни name, ни params | — |
view_not_found | 404 | представления нет | — |
views_limit | 409 | у вас на сайте уже 50 сохраненных представлений | удалите ненужный |
Markdown-копии страниц
Каждая опубликованная страница открывается ещё и чистым текстом по своему адресу с .md вместо завершающего слеша: https://example.com/docs/start/ → https://example.com/docs/start.md, главная — /index.md, главная языка — /ru/index.md. Токен не нужен. Внутри H1, выдержка (без неё — SEO-описание) и тело в markdown, ссылки и картинки полными адресами; у страницы без тела — список дочерних страниц со ссылками на их .md. На копии ссылается llms.txt. Ответ — 200 text/plain; charset=utf-8, кэш до минуты. 404 отвечают неопубликованные страницы, страницы под паролем, закрытые от индексации (noindex), страницы без фрейма или с неопубликованным родителем и все копии закрытого сайта. Копия обновляется сама при публикации, правке, смене статуса, переносе и удалении страницы.
GET /markdown
Возвращает состояние копий сайта. Например:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/markdown"
{ "success": true, "site_id": "ef7QmgyEc", "host": "onpress.pro", "site_closed": false, "pages_in_manifest": 228, "files": 228, "index_url": "https://onpress.pro/index.md", "llms_auto": false }
files — сколько копий собрано, pages_in_manifest — для скольких страниц, llms_auto — llms.txt собран автоматически и пересобирается сам. Копии, которых ещё нет, собираются при первом запросе.
POST /markdown/build
Собирает копии всех публичных страниц разом и, если не передано "llms": false, llms.txt; на большом сайте ответ приходит до двух минут. Например:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
llms | bool | true | собрать заодно llms.txt; свой текст llms.txt не трогается |
curl -s -X POST "$API/site/$SITE/markdown/build" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' -d '{}'
{ "success": true, "site_id": "SITE_ID", "pages": 72, "written": 60, "skipped": 12, "failed": 0, "removed": 0, "llms": { "rebuilt": true, "pages": 60, "bytes": 18140 }, "files": 60, "llms_auto": true }
written — собрано копий, skipped — пропущены непубличные, failed — не записались, removed — сняты копии исчезнувших страниц. llms.rebuilt: false с reason: "custom_text" — у сайта свой текст llms.txt, "nothing_to_list" — публичных страниц нет.
| Код | HTTP | Когда | Что делать |
|---|---|---|---|
markdown_missing | 500 | на сайте не включена сборка копий | — |