Справочник: фреймы
Все ручки фреймов: фреймы сайта, их файлы стилей и скриптов, библиотека системных фреймов. В примерах $API — https://onpress.pro/api/v2, $SITE — id сайта (SITE_ID), $OP_TOKEN — токен вида op_XXXX. Ручки сайта живут под $API/site/$SITE/frames…, ручки библиотеки — под $API/frames/system…, без сайта в адресе. {ref} — id фрейма (16 шестнадцатеричных символов) или слаг. Что такое фрейм и как он устроен — фреймы. Фреймы, которые вызываются в markdown страницы и обрамляют текст (шаги, FAQ, врезки), — фреймы в markdown, готовые фреймы с параметрами и списками — фреймы с данными.
Общее для всех записей фрейма:
typeво входе игнорируется (warnings: ["type_ignored"]),default/is_default— тоже (default_ignored),name— тоже (name_ignored);urlво входе —400 frame_url_not_supported: у фрейма нет адреса;bake: false— не просить сборку на стороне сайта (по умолчанию просится);dry_run: true— проверить и показать результат без записи; то же значит?dry_run=1в строке запроса. Ручка без пробного прогона (поставить на страницу, пересобрать вручную) наdry_runотвечает400 dry_run_not_supportedи ничего не пишет;- ответ записи несёт
bake(сборка этого фрейма) иrebaked(пересборка фреймов, вкладывающих его).
Фреймы сайта
| Пункт | Ручка | Тело |
|---|---|---|
| Список | GET /site/{siteId}/frames | ?block=article&q=head&include_html=1 |
| Загрузить | POST /site/{siteId}/frames | {"slug": "site__header", "html": "…"} |
| Прочитать | GET /site/{siteId}/frames/{ref} | — |
| Заменить разметку | PUT /site/{siteId}/frames/{ref} | {"html": "…"} |
| Переименовать | PATCH /site/{siteId}/frames/{ref} | {"slug": "article__faq"} |
| Удалить | DELETE /site/{siteId}/frames/{ref} | {}, {"bake": false} или ?dry_run=1 |
| Поставить на страницу | POST /site/{siteId}/frames/{ref}/assign | {"page": "/tarify/"} |
| Пересобрать вручную | POST /site/{siteId}/frames/rebake | {"datasets": ["faq"]} |
Без комментариев
Разметка фрейма и его файлы уходят в браузер как есть, поэтому комментариев в них быть не должно: ни <!-- … --> в разметке, ни // и /* … */ во встроенных и подключаемых скриптах, ни /* … */ в стилях, ни кириллицы в коде вне строк. Загрузка, замена разметки (в том числе системной версии) и запись файла с комментарием отклоняются целиком с 422 comments_not_allowed — ничего не записывается. Пояснения держите вне фрейма.
Пропускается то, что комментарием только выглядит и до страницы не доходит или обязано остаться:
| Что | Пример |
|---|---|
| вставки и их закрытие | <!-- onpress/frame {"ref":"site__header"} /-->, <!-- onpress/repeat {"source":"faq"} --> … <!-- /onpress/repeat --> |
| разделители блоков | <!-- wp:paragraph --> … <!-- /wp:paragraph --> |
| лицензионные заголовки | /*! MIT License … */ |
| условные комментарии IE | <!--[if lt IE 9]> |
| адреса и строки | "https://example.com", '// не комментарий' |
{
"success": false,
"error": "comments_not_allowed",
"message": "frame markup has 1 comment(s) at line(s) 2: code that goes to the browser carries no comments …",
"kind": "html",
"count": 1,
"found": [{ "kind": "html", "line": 2, "text": "<!-- note -->" }]
}
kind — что проверялось: html (разметка), css или js (файл). found — до 20 находок: kind (html, line — //, block — /* */, cyrillic), номер строки и сама находка. Проверка идёт и при dry_run.
Список
| Параметр | Смысл |
|---|---|
block | только фреймы блока: ?block=article — article, article__faq, article__faq--simple |
q | подстрока слага, без учёта регистра |
include_html | 1 — с разметкой каждого фрейма |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/frames?q=site__"
{
"success": true,
"site_id": "SITE_ID",
"home_url": "https://example.com",
"total": 2,
"frames": [
{
"id": "10bc8f3cd3f65cd0",
"slug": "site__header",
"block": "site",
"element": "header",
"modifier": null,
"bytes": 2117,
"sha256": "…",
"inserts": { "op/repeat": 3, "op/lang-switcher": 1 },
"sources": ["header", "menu.header", "item.children"],
"frame_refs": [],
"binds": 9,
"files": { "style.css": { "sha256": "…", "bytes": 1840, "type": "css" } },
"created_at_gmt": "2026-10-01 16:53:49",
"updated_at_gmt": "2026-10-01 16:53:49",
"updated_by": 1,
"option": "onpress_frame_10bc8f3cd3f65cd0",
"origin": "system",
"system_version": 7,
"system_updated_at_gmt": "2026-10-02 08:40:12",
"system_update_available": false,
"resettable": false,
"preview_page": { "id": 7036, "url": "/home/", "link": "https://example.com/home/" }
}
],
"registry_option": "onpress_frames",
"page_meta": "_onpress_frame",
"slug_pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?$",
"realm": "prod",
"instance_realm": "prod",
"writable": true,
"is_base": false
}
Фреймы отсортированы по слагу. Поля фрейма:
| Поле | Смысл |
|---|---|
pages | id страниц, у которых это их фрейм; нет страниц — поля нет |
preview_page | первая опубликованная страница, где фрейм реально выводится: своя страница, страница фрейма, который его вкладывает (по цепочке), или страница, вставившая его в тело; {id, url, link}. Есть и у вложенных фреймов (site__header, author__byline), у которых своих страниц нет. Фрейм нигде не выводится — поля нет |
files | опись файлов фрейма: {имя: {sha256, bytes, type}}; без файлов поля нет |
renders_on_request | true — фрейм зависит от страницы и собирается на каждом запросе |
origin, system_version, resettable и др. | происхождение, см. системные и свои фреймы |
Поля сайта рядом со списком:
| Поле | Смысл |
|---|---|
realm | контур сайта: prod — боевой, stage — стенд |
instance_realm | контур сервиса, который ответил |
writable | запись в этот сайт отсюда пройдёт; false — сайт другого контура, читать можно, запись получит 409 wrong_realm |
is_base | сайт — эталон: правка его фреймов выпускает новую системную версию |
?type= игнорируется с предупреждением type_ignored.
Загрузить
| Поле | Тип | Смысл |
|---|---|---|
slug | string | обязательно; блок__элемент--модификатор, приводится к нижнему регистру |
html | string | обязательно; синонимы markup, content. Хранится байт в байт |
dry_run | bool | проверить и вернуть отчёт без записи |
bake | bool | просить сборку (по умолчанию true) |
curl -s -X POST "$API/site/$SITE/frames" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d "$(jq -n --arg html '<section class="proba-card"><h2 data-op-bind='"'"'{"text":"site.name"}'"'"'></h2><!-- onpress/menu {"place":"header","class":"proba-card__menu"} /--></section>' '{slug: "docs-proba__kartochka", html: $html}')"
{
"success": true,
"applied": true,
"updated": false,
"site_id": "SITE_ID",
"frame": {
"id": "20a39aa2b124c145",
"slug": "docs-proba__kartochka",
"bytes": 146,
"sha256": "…",
"inserts": { "op/menu": 1 },
"sources": [],
"frame_refs": [],
"binds": 1,
"created_at_gmt": "2026-09-25 20:18:25",
"updated_at_gmt": "2026-09-25 20:18:25",
"updated_by": 1,
"block": "docs-proba",
"element": "kartochka",
"modifier": null,
"option": "onpress_frame_20a39aa2b124c145",
"origin": "custom",
"system_update_available": false,
"resettable": false
},
"inserts": { "op/menu": 1 },
"inserts_total": 1,
"sources": [],
"frame_refs": [],
"binds": 1,
"renders_on_request": false,
"has_doctype": false,
"has_body": false,
"warnings": [],
"bake": {
"status": "ok",
"response": {
"baked": true,
"frame_id": "20a39aa2b124c145",
"bytes": 685,
"source_bytes": 146,
"baked_option": "onpress_frame_20a39aa2b124c145_baked",
"renders_on_request": false,
"blocks": 1,
"inserts": 1,
"unknown_inserts": [],
"binds": 1,
"repeats": [],
"frames": [],
"unresolved": [],
"warnings": [],
"published": { "published": false, "pages": [] },
"pages": []
}
}
}
Отчёт о разметке: inserts, inserts_total, sources, frame_refs (ссылки по id приведены к слагам), binds, renders_on_request и live_reasons, has_doctype, has_body, warnings, frame_refs_missing. Ключи inserts называют вставки коротким именем — так их считает отчёт, в разметке пишите полную форму onpress/…. Что значат поля bake.response — в сборке. dry_run отвечает тем же отчётом с dry_run: true, applied: false и без bake.
На эталонном сайте запись идёт сквозь в библиотеку и на все сайты, где фрейм системный: ответ несёт ещё поле system. На остальных сайтах правка системного фрейма делает его своим.
Отказы: 400 invalid_slug (pattern, hint), 400 frame_html_required, 422 comments_not_allowed (без комментариев), 400 frame_url_not_supported, 400 frame_ref_conflict (во вставке onpress/frame два ключа ссылки называют разные фреймы; conflicts — пары ключей и значений; слаг и id одного фрейма конфликтом не считаются), 409 slug_taken (id занявшего — замените его разметку через PUT), 413 frame_too_large (больше 2 МБ; max_bytes), 403 insufficient_role (запись на эталоне не суперадмином сети), 409 wrong_realm (сайт другого контура).
Прочитать
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/frames/site__header" | jq '.frame | {id, slug, referenced_by, pages, files, html}'
Ответ {success, frame}: всё, что в элементе списка (с полями происхождения и описью files), плюс html — разметка, которую фрейм выводит сейчас (у системного — версия библиотеки), referenced_by — слаги фреймов, которые его вкладывают, pages — id страниц на этом фрейме. Содержимое файлов здесь не отдаётся — его читают ручкой файла. Отказы: 404 frame_not_found, 400 frame_ref_required.
Заменить разметку
Тело — как у загрузки (html обязателен, slug можно сменить — с теми же последствиями, что у переименования). Ответ — как у загрузки с updated: true и rebaked — пересборкой фреймов, вкладывающих этот (транзитивно). Файлы фрейма правка разметки не трогает. Новая разметка у системного фрейма делает его своим (origin: "custom", customized_at_gmt): правка остаётся на этом сайте, обновления платформы до него больше не доходят. Разметка, совпавшая с системной с точностью до пробелов, оставляет его системным. На эталоне разметка тоже становится новой версией библиотеки — см. эталонный сайт. Отказы: как у загрузки плюс 404 frame_not_found.
Переименовать
| Поле | Смысл |
|---|---|
slug | новый слаг; единственное поле, которое правит PATCH |
dry_run, bake | как у загрузки |
curl -s -X PATCH "$API/site/$SITE/frames/docs-proba__kartochka" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"slug": "docs-proba__card"}'
{
"success": true,
"site_id": "SITE_ID",
"frame": { "id": "20a39aa2b124c145", "slug": "docs-proba__card", "block": "docs-proba", "element": "card", "modifier": null, "…": "…" },
"renamed": {
"from": "docs-proba__kartochka",
"to": "docs-proba__card",
"frames_updated": ["docs-proba--lending"],
"details": [ { "id": "53c0ec423d1f7072", "slug": "docs-proba--lending", "replaced": 1 } ]
},
"bake": { "status": "ok" },
"rebaked": { "status": "ok", "…": "…" }
}
Во фреймах сайта, ссылавшихся на старый слаг, ссылки onpress/frame (и устаревшей onpress/partial) переписываются на новый; ссылка, записанная алиасом ключа, переписывается основным ключом name, если его во вставке ещё нет; остальная разметка не меняется. Страницы не трогаются — они хранят id. Новый слаг у системного фрейма делает его своим. На эталоне слаг фрейма библиотеки меняется везде — см. эталонный сайт. Отказы: 400 nothing_to_update (в теле только name или ничего), 410 gone (только default/is_default), 400 invalid_slug, 409 slug_taken, 400 frame_url_not_supported, 404 frame_not_found.
Удалить
curl -s -X DELETE "$API/site/$SITE/frames/docs-proba__faq" -H "Authorization: Bearer $OP_TOKEN"
{
"success": true,
"site_id": "SITE_ID",
"deleted": { "id": "838a082b034be249", "slug": "docs-proba__faq", "block": "docs-proba", "element": "faq", "modifier": null },
"unbound_pages": [],
"referenced_by": ["docs-proba"],
"warnings": ["frames docs-proba still insert \"docs-proba__faq\"; the insert prints nothing until a frame with this slug appears again"],
"bake": { "status": "ok" }
}
Уходят разметка, файлы фрейма и запись реестра. У страниц фрейма снимается привязка (unbound_pages) и статическая копия — на сайте v2 они начинают отвечать 404. Вкладывающие фреймы не правятся (referenced_by). bake: false в теле — не просить сборку. Удаление с эталона библиотеку не трогает: сайты, где фрейм системный, выводят его по-прежнему, в warnings — system_frame_kept: …; убрать фрейм из библиотеки — отдельная ручка. Отказы: 404 frame_not_found.
Пробное удаление. dry_run в теле или ?dry_run=1 в строке запроса ничего не удаляет и отвечает планом: что уйдёт с сайта (would_delete.files, would_delete.unbound_pages — страницы, которые потеряют фрейм и статическую копию), кто продолжит на него ссылаться (referenced_by — фреймы со вставкой, affected_frames — они же вместе с вкладывающими их, body_pages — страницы, чьё тело зовёт фрейм по имени) и что будет с библиотекой (library.kept: true — у системного фрейма библиотека и другие сайты не затрагиваются).
curl -s -X DELETE "$API/site/$SITE/frames/promo__probe?dry_run=1" -H "Authorization: Bearer $OP_TOKEN"
{
"success": true,
"dry_run": true,
"applied": false,
"frame": { "id": "26824a8c18220eb4", "slug": "promo__probe", "block": "promo", "element": "probe", "modifier": null, "origin": "custom" },
"would_delete": { "sites": ["SITE_ID"], "files": ["style.css"], "unbound_pages": [] },
"referenced_by": ["promo__wrap"],
"affected_frames": ["promo__wrap"],
"body_pages": [{ "id": 7076, "title": "Проба", "status": "publish", "path": "/proba/" }],
"warnings": [
"frames promo__wrap still insert \"promo__probe\"; the insert outputs nothing until a frame with this slug appears again",
"the bodies of pages 7076 call \"promo__probe\"; the call outputs nothing after the delete"
]
}
Поставить на страницу
| Поле | Смысл |
|---|---|
page | обязательно: id, слаг или путь страницы |
clear | true — снять фрейм со страницы |
bake | как у загрузки |
curl -s -X POST "$API/site/$SITE/frames/docs-proba--lending/assign" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"page": "proba-api"}'
{
"success": true,
"site_id": "SITE_ID",
"frame": { "id": "53c0ec423d1f7072", "slug": "docs-proba--lending", "…": "…" },
"page": { "id": 6428, "title": "Проба API", "url": "/proba-api/" },
"meta_key": "_onpress_frame",
"cleared": false,
"static": { "published": true, "url": "/proba-api", "bytes": 15368, "reason": "", "message": "", "was_published": true, "was_url": "/proba-api", "removed": false, "changed": false },
"republish": { "status": "ok", "…": "…" }
}
static — судьба статической копии этой страницы после смены фрейма (поля как у PATCH /pages/{ref}), republish.status — ok, skipped, unavailable, failed; неудача не отменяет привязку. Пробного прогона у ручки нет. Отказы: 400 dry_run_not_supported, 400 page_required, 404 frame_not_found, 404 page_not_found, 409 ambiguous_page.
Пересобрать вручную
| Поле | Кого пересобрать |
|---|---|
all | все фреймы сайта и статические копии страниц |
menus | зависимые от меню |
pages | выводящие страницы, их заголовки и адреса |
languages | языкозависимые |
settings | зависимые от настроек сайта |
datasets | массив имён наборов — читающие их |
frames | массив слагов или id — вкладывающие их |
curl -s -X POST "$API/site/$SITE/frames/rebake" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"datasets": ["docs-proba-faq"]}'
{
"success": true,
"site_id": "SITE_ID",
"changed": { "datasets": ["docs-proba-faq"] },
"rebaked": {
"status": "ok",
"total": 2,
"baked": 0,
"not_needed": 2,
"frames": [
{ "id": "838a082b034be249", "slug": "docs-proba__faq", "because": ["datasets: docs-proba-faq"], "bake": "not_needed", "detail": "renders on request from the source, has no baked copy" },
{ "id": "e627bf4dbefcc374", "slug": "docs-proba", "because": ["contains docs-proba__faq"], "bake": "not_needed", "detail": "renders on request from the source, has no baked copy" }
],
"cache": {
"status": "ok",
"scope": "pages",
"pages": [6428],
"flushed": { "urls": { "status": "flushed", "by": "wpsc_delete_url_cache", "urls": ["https://example.com/proba-api/"], "skipped": [] } }
}
}
}
Поля rebaked — в сборке. За вызов — не больше 25 фреймов (остальные в skipped).
С {"all": true} заодно собираются заново все статические копии страниц сайта — те, что отдаются готовым файлом. Это нужно после обновления платформы: в копии заморожены шапка и подвал страницы на момент сборки. Итог — в поле static:
"static": {
"status": "ok",
"total": 1,
"baked": 1,
"static": { "status": "ok", "pages": [{ "id": 7036, "published": true, "url": "/home-next" }] }
}
status — ok, partial (часть страниц не собралась, подробности в failures) или failed; у сайта без статических копий — nothing с reason. total — страниц со статической копией, baked — собранных заново; больше 200 за вызов не собирается (остальные в skipped, предел в cap). pages — по странице: id, published, url; страница, которой копия больше не положена, снимается с публикации. Без all поля static нет. Отказы: 400 nothing_to_rebake, 400 dry_run_not_supported.
Файлы фрейма
| Пункт | Ручка | Тело |
|---|---|---|
| Прочитать файл | GET /site/{siteId}/frames/{ref}/files?name={name} | — |
| Записать файл | PUT /site/{siteId}/frames/{ref}/files?name={name} | {"content": ".card{padding:24px}"} |
| Переименовать файл | PATCH /site/{siteId}/frames/{ref}/files?name={name} | {"name": "main.js"} |
| Удалить файл | DELETE /site/{siteId}/frames/{ref}/files?name={name} | {} |
Рядом с разметкой фрейм может держать свои стили и скрипты — файлы .css и .js. Опись — поле files фрейма в списке и в карточке: {имя: {sha256, bytes, type}}, type — css или js. Имя файла передают параметром name: адрес с именем в пути (…/files/style.css) перехватывает раздача статики, и он отвечает 404.
Как они попадают на страницу. Стили всех фреймов, которые выводятся на странице, — самого фрейма страницы, вложенных, вставленных в тело и в повторители — собираются в один блок <style id="op-frames-css"> в конце <head>, после стилей сайта: отдельного запроса на фрейм нет, вспышки без стилей нет, фрейм, выведенный на странице несколько раз, приносит свои правила один раз. Правила идут в порядке, в котором фреймы встречаются на странице, css-файлы одного фрейма — по имени. На страницу уходит сжатая копия: style.css хранится как записан, а при записи платформа сжимает его (пробелы, комментарии, короткие записи цветов и чисел) — размер сжатой копии в ответе записи, поле minified_bytes. Скрипты подключаются файлом: один <script> на фрейм перед </body>, адрес с хешем содержимого, правка видна сразу без сброса кэша. Фрейм — целый документ получает стили в свой <head>, скрипты — перед </body>.
Стили — в файле, а не в разметке. Блок <style> в разметке фрейма повторяется на странице столько раз, сколько раз выведен фрейм, и идёт несжатым; тот же css в style.css приходит один раз и сжатым. Комментарии в css не нужны — их всё равно срезает сжатие; метки кусков пишутся сторожами :root{--имя:1} … :root{--имя-end:1}.
Пределы: имя — латиница в нижнем регистре, цифры, - и _, до 48 символов, расширение .css или .js (style.css, script.js); файл — до 512 КБ; у фрейма — до 16 файлов. Картинки и шрифты в файлы не кладут — они живут в медиатеке, а в css на них ссылаются по адресу.
Происхождение. Правка файла у системного фрейма делает его своим, как правка разметки; «Вернуть системный» и сброс к системному возвращает и разметку, и файлы библиотеки. На эталоне правка файла выпускает новую системную версию. Новая версия библиотеки, установка из неё и отвязка переносят файлы вместе с разметкой.
Каждая запись пересобирает страницы фрейма (bake: false — не пересобирать) и снимает их кэш — поле cache, как у сброса к системному. dry_run: true показывает фрейм после правки без записи.
Прочитать файл
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/frames/docs-proba__files/files?name=style.css"
{
"success": true,
"site_id": "SITE_ID",
"frame": { "id": "380c8a1119a967ca", "slug": "docs-proba__files" },
"file": { "name": "style.css", "sha256": "…", "bytes": 32, "type": "css", "content": ".docs-proba-files{padding:32px}\n" }
}
Отказы: 404 frame_not_found, 404 file_not_found, 400 invalid_file_name.
Записать файл
Создаёт файл или заменяет его содержимое целиком.
| Поле | Тип | Смысл |
|---|---|---|
content | string | обязательно; текст файла |
bake, dry_run | bool | как у загрузки фрейма |
curl -s -X PUT "$API/site/$SITE/frames/docs-proba__files/files?name=style.css" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"content": ".docs-proba-files{padding:24px}\n"}'
{
"success": true,
"applied": true,
"site_id": "SITE_ID",
"frame": {
"id": "380c8a1119a967ca",
"slug": "docs-proba__files",
"files": { "style.css": { "sha256": "…", "bytes": 32, "type": "css" } },
"origin": "custom",
"…": "…"
},
"file": { "name": "style.css", "sha256": "…", "bytes": 32, "type": "css", "created": true, "minified_bytes": 31 },
"pages": [],
"bake": { "status": "ok", "…": "…" },
"cache": { "status": "nothing", "reason": "no page prints this frame — there is no page cache to drop" }
}
file.created — true, если файла не было; file.minified_bytes — размер сжатой копии, которая уходит на страницу (у .css; нет поля — css не разобрался, и на страницу идёт как записан). Отказы: 400 invalid_file_name (pattern, hint), 400 file_content_required, 422 comments_not_allowed (без комментариев), 400 too_many_files (max_files), 413 file_too_large (bytes, max_bytes), 404 frame_not_found, 403 insufficient_role (на эталоне не суперадмином).
Переименовать файл
PATCH с {"name": "<новое имя>"}: содержимое переезжает под новое имя, расширение можно сменить в пределах .css и .js. Ответ — как у записи, вместо file — renamed: {from, to}.
{ "success": true, "applied": true, "renamed": { "from": "script.js", "to": "main.js" }, "frame": { "files": { "main.js": { "…": "…" }, "style.css": { "…": "…" } }, "…": "…" }, "bake": { "status": "ok" } }
Отказы: 404 file_not_found, 409 file_exists (имя занято другим файлом), 400 invalid_file_name.
Удалить файл
curl -s -X DELETE "$API/site/$SITE/frames/docs-proba__files/files?name=main.js" -H "Authorization: Bearer $OP_TOKEN"
Ответ — как у записи, вместо file — deleted: "main.js"; в frame.files файла больше нет. bake и dry_run можно передать в теле или в строке запроса. Отказы: 404 file_not_found, 400 invalid_file_name.
Системные и свои фреймы
| Пункт | Ручка | Тело |
|---|---|---|
| Происхождение фрейма | GET /site/{siteId}/framesGET /site/{siteId}/frames/{ref} | — |
| Правка системного фрейма | PUT /site/{siteId}/frames/{ref}PATCH /site/{siteId}/frames/{ref} | {"html": "…"} или {"slug": "…"} |
| Вернуть системный | POST /site/{siteId}/frames/{ref}/revert | {} или {"dry_run": true} |
| Сделать своим без правки | POST /site/{siteId}/frames/{ref}/detach | {} |
| Сбросить к системному | POST /site/{siteId}/frames/{ref}/reset | {} или {"dry_run": true} |
| Эталонный сайт | PUT /site/{siteId}/frames/{ref} на эталоне | {"html": "…"} |
| Библиотека системных фреймов | GET /frames/system | ?sites=1 |
| Системный фрейм | GET /frames/system/{ref}PUT /frames/system/{ref} | {"html": "…"} |
| Сделать фрейм сайта системным | POST /frames/system | {"blog_id": 268, "frame": "site__header"} |
| Поставить фрейм библиотеки на сайт | POST /frames/system/{ref}/install | {"blog_id": 233} или {"all": true} |
| Убрать из библиотеки | DELETE /frames/system/{ref} | {"dry_run": true} |
У платформы есть общая библиотека фреймов — системные фреймы. Новый сайт получает их системными: фрейм выводит общую версию, и когда её обновляют, обновление сразу доходит до сайта. Как только разметку, файлы или слаг фрейма поменяли на самом сайте (ручки выше, дашборд, агент), фрейм становится своим: правки сайта остаются, обновления библиотеки его больше не касаются. Так для всех, включая администратора платформы; новую версию для всех сайтов выпускает только платформа — правкой на эталоне или ручкой библиотеки. «Вернуть системный» ставит фрейм обратно.
Ручки сайта пишут только в свой сайт; ручки библиотеки — без сайта в адресе, читать их может любой пользователь, писать — только суперадмин сети (403 insufficient_role). Стенд и боевой контур держат библиотеки раздельно: новая версия доходит только до сайтов своего контура, сайты другого перечислены в skipped_other_contour.
Раскатка новой версии, установка, сброс и отвязка сразу снимают кэш страниц, где фрейм выводится: страниц на самом фрейме, страниц фреймов, которые его вкладывают (по цепочке), и страниц, вставивших его в тело. Поле cache у каждого сайта:
| Поле | Смысл |
|---|---|
status | ok — кэш снят; nothing — фрейм не выводится ни на одной странице, снимать нечего; failed — не удалось, reason |
scope | pages — сняты страницы поштучно; site — снят кэш всего сайта: страниц больше 400 или их не удалось счесть |
pages | сколько страниц снято (у сброса к системному — их id) |
Происхождение фрейма
Поля в каждом фрейме ответа списка и карточки:
| Поле | Смысл |
|---|---|
origin | system — фрейм выводит версию библиотеки; custom — свой фрейм сайта |
system_version | у системного — версия библиотеки, которую он выводит; у своего, отошедшего от системного, — версия, от которой отошёл; у фрейма, заведённого на сайте, поля нет |
system_updated_at_gmt | когда в библиотеке вышла текущая версия; только если фрейм есть в библиотеке |
customized_at_gmt | когда фрейм стал своим; только у изменённого системного |
system_update_available | у своего фрейма в библиотеке вышла версия новее той, от которой он отошёл |
resettable | фрейм можно сбросить к системному: он свой, а в библиотеке есть его версия |
{
"id": "cb7ccee58078d1fb",
"slug": "site__header",
"…": "…",
"origin": "custom",
"system_version": 1,
"system_updated_at_gmt": "2026-09-30 08:11:25",
"customized_at_gmt": "2026-09-13 11:34:34",
"system_update_available": false,
"resettable": true
}
Правка системного фрейма
Замена разметки, переименование и правка файлов системного фрейма на любом сайте, кроме эталона, делают его своим: origin: "custom", system_version — версия, от которой он отошёл, customized_at_gmt — когда. Новые версии библиотеки его пропускают (в их ответе он в custom_sites). Разметка и файлы, совпавшие с библиотекой, при том же слаге — обычная запись, фрейм остаётся системным. Если переименование своего фрейма переписало ссылку на него в системных фреймах сайта, они тоже становятся своими: renamed.details[].detached и предупреждение system_frames_detached.
Раскатить правку на все сайты, где фрейм системный, можно только платформенным путём: записью на эталоне или ручкой библиотеки PUT /frames/system/{ref} — см. системный фрейм.
Сделать фрейм своим, ничего не меняя, — POST /frames/{ref}/detach: разметка и файлы те же, но обновления платформы до него больше не доходят. Ответ {"detached": true, "frame": …, "cache": …}, у своего — already_custom: true. Отказы: 409 frame_not_system, 409 base_blog (эталон).
Вернуть системный
POST /frames/{ref}/revert ставит своему фрейму текущую версию библиотеки: фрейм снова системный, страницы пересобираются, кэш снимается. Подходит фрейм, у которого в библиотеке есть версия с тем же id (такой фрейм отошёл от системного) или с тем же слагом (у фрейма свой id, он заменяется библиотечным: привязки страниц переезжают на новый id, ссылки по слагу не рвутся). Такие фреймы в списке отмечены resettable: true, второй случай — ещё и system_slug_match: true. Своя разметка и файлы перед возвратом сохраняются в снимок — поле snapshot.
curl -s -X POST "$API/site/$SITE/frames/author__head/revert" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' -d '{}'
{
"success": true,
"site_id": "SITE_ID",
"reverted": true,
"mode": "reset",
"frame": { "id": "308bb11594598ddd", "slug": "author__head", "origin": "system", "system_version": 7, "…": "…" },
"bake": { "status": "ok", "…": "…" },
"cache": { "status": "nothing" },
"snapshot": { "option": "onpress_frames_reverted_308bb11594598ddd", "saved_at_gmt": "2026-10-02 19:03:59" }
}
mode — reset (тот же id, дальше как у сброса: pages, rebaked) или replace (тот же слаг: frame.id — уже библиотечный, replaced — прежний фрейм, pages_moved — сколько страниц переехало, installed — вложенные фреймы, которых на сайте не было). Фрейм уже системный — reverted: false, already_system: true. dry_run: true отвечает revert: {mode, version, library_id} без записи.
Отказы: 404 frame_not_found, 409 frame_not_system (в библиотеке нет фрейма ни с этим id, ни с этим слагом), 409 base_blog (эталон), 409 system_frame_missing.
Сбросить к системному
POST /frames/{ref}/reset выбрасывает правки сайта: фрейм снова выводит версию библиотеки вместе с её файлами и дальше получает её обновления. Заодно пересобираются он сам и фреймы, которые его вкладывают, и снимается кэш их страниц. dry_run: true показывает фрейм после сброса без записи, bake: false — не пересобирать.
curl -s -X POST "$API/site/$SITE/frames/site__header/reset" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' -d '{}'
{
"success": true,
"site_id": "SITE_ID",
"reset": true,
"frame": { "id": "cb7ccee58078d1fb", "slug": "site__header", "…": "…", "origin": "system", "system_version": 1, "system_update_available": false, "resettable": false },
"pages": [6191, 6238],
"bake": { "status": "ok", "…": "…" },
"rebaked": { "status": "ok", "…": "…" },
"cache": { "status": "ok", "scope": "pages", "pages": [6191, 6238], "flushed": { "…": "…" } }
}
Фрейм уже системный — 200 с reset: false и already_system: true, ничего не пишется. pages — страницы на этом фрейме, bake — как у замены разметки, rebaked приходит, если фрейм кто-то вкладывает, cache — сброс кэша страниц.
Отказы: 404 frame_not_found, 409 frame_not_system (фрейм заведён на сайте, системной версии у него нет; id, slug), 409 system_frame_missing (фрейм есть в библиотеке, но разметки его версии нет).
Эталонный сайт
Библиотека ведётся с одного сайта — эталона. Для боевого контура это base-v2.onpress.pro; его номер отдают GET /frames/system (поле base_blog) и список фреймов самого эталона (is_base: true). Пока эталон назначен:
- Правка фрейма на эталоне — это новая системная версия. Загрузка, замена разметки, переименование и правка файлов на эталоне не делают фрейм своим: он остаётся системным, его разметка и файлы сразу уходят в библиотеку новой версией и раскатываются на все сайты, где фрейм системный. Писать во фреймы эталона может только суперадмин сети.
- Публиковать в библиотеку и убирать из неё — только с эталона.
POST /frames/systemс другого сайта —409 not_base_blog;DELETE /frames/system/{ref}сblog_idдругого сайта — тоже. - Новый сайт клонируется с эталона.
POST /sitesбезdonorберёт эталон донором — подробно в справочнике сайтов. - На сам эталон из библиотеки не ставят —
409 base_blog, фрейм библиотеки и так взят с него.
Ответ записи на эталоне — как у обычной записи плюс поле system:
{
"success": true,
"applied": true,
"updated": true,
"site_id": "SITE_ID",
"frame": { "id": "380c8a1119a967ca", "slug": "docs-proba__files", "origin": "system", "system_version": 4, "…": "…" },
"bake": { "status": "ok" },
"system": {
"published": true,
"version": 4,
"updated_sites": [
{ "site_id": "SITE_ID", "slug": "docs-proba__files", "bake": "ok", "cache": { "status": "ok", "scope": "pages", "pages": 1 } }
],
"custom_sites": []
},
"warnings": []
}
system.updated_sites и custom_sites — как у новой версии системного фрейма. Если запись на эталоне прошла, а библиотека не обновилась, ответ всё равно 200: system.published: false, system.error и предупреждение system_publish_failed: … в warnings. Разметка уже на эталоне — повторите публикацию POST /frames/system с номером эталона и слагом фрейма.
Переименование фрейма библиотеки на эталоне меняет слаг везде: на эталоне, в библиотеке и на всех сайтах, где фрейм системный; фреймы, у которых переписалась ссылка, своими не становятся и выходят в библиотеке новой версией (в ответе system.renamed_sites и system.republished). Сделать фрейм эталона своим или вернуть системным нельзя (409 base_blog); удаление с эталона библиотеку не трогает (system_frame_kept). Эталона нет — ограничений нет: публиковать в библиотеку можно с любого сайта своего контура.
Библиотека системных фреймов
GET /frames/system — все системные фреймы, по слагу.
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/frames/system?sites=1"
{
"success": true,
"total": 50,
"registry_option": "onpress_system_frames",
"base_blog": 268,
"frames": [
{
"id": "c13525876cadfc4b",
"slug": "about",
"block": "about",
"element": null,
"modifier": null,
"version": 13,
"bytes": 14442,
"sha256": "…",
"updated_at_gmt": "2026-10-02 11:01:41",
"updated_by": 1,
"source_blog": 268,
"sites": { "system": [236, 264], "custom": [], "other_contour": [233] }
}
]
}
version растёт на единицу с каждой новой версией, source_blog — сайт, с которого фрейм попал в библиотеку, base_blog — эталон своего контура (null — не назначен). У фрейма с файлами есть files (опись, как у фрейма сайта) и bundle_sha256 — общий хеш разметки и файлов: по нему видно, что версия поменялась, даже если разметка та же. ?sites=1 добавляет sites: номера сайтов, где фрейм системный (system), где свой (custom), и сайты другого контура (other_contour).
Системный фрейм
GET /frames/system/{ref} — один системный фрейм (id или слаг): поля из списка, всегда с sites, и html — разметка текущей версии. Отказы: 404 system_frame_not_found.
PUT /frames/system/{ref} с {"html": "…"} (синонимы markup, content) выпускает новую версию: version + 1, файлы версии остаются прежними, и новая разметка сразу доходит до всех сайтов своего контура, где фрейм системный, — с пересборкой фрейма и вкладывающих его и сбросом кэша их страниц. Свои фреймы не трогаются и перечислены в custom_sites. Та же разметка и те же файлы, что уже в библиотеке, — 200 с applied: false, unchanged: true, версия не растёт. dry_run: true показывает будущую версию и сайты без записи, bake: false — не пересобирать.
curl -s -X PUT "$API/frames/system/docs-proba__files" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d "$(jq -n --rawfile html docs-proba__files.html '{html: $html}')"
{
"success": true,
"applied": true,
"frame": { "id": "380c8a1119a967ca", "slug": "docs-proba__files", "version": 3, "bytes": 141, "sha256": "…", "updated_at_gmt": "2026-10-02 16:57:15", "updated_by": 1, "source_blog": 268, "…": "…" },
"updated_sites": [
{
"blog_id": 233,
"slug": "docs-proba__files",
"bake": "ok",
"cache": { "status": "ok", "scope": "pages", "pages": 1 },
"installed": [
{ "blog_id": 233, "slug": "docs-proba__inner", "status": "installed", "bake": "ok", "cache": { "status": "nothing", "reason": "no page prints this frame — there is no page cache to drop" } }
]
}
],
"custom_sites": []
}
У каждой строки updated_sites: bake — исход сборки (ok, failed…), cache — сброс кэша страниц, error — если сайт обновить не удалось.
Вложенные фреймы доезжают сами. Если новая версия вкладывает через onpress/frame фрейм, которого на сайте нет, а в библиотеке он есть, раскатка тут же ставит его на сайт системным — строкой в installed (поля как у установки). Поставленный фрейм проверяется так же, до 4 уровней вложения. Эталону ничего не ставится, слаг, занятый своим фреймом сайта, отсутствующим не считается. То же делают запись на эталоне и POST /frames/system.
Отказы: 403 insufficient_role, 400 frame_html_required, 404 system_frame_not_found (такого фрейма в библиотеке нет — сначала POST /frames/system), 413 frame_too_large.
Сделать фрейм сайта системным
POST /frames/system с {"blog_id": …, "frame": "<слаг или id>"} (вместо blog_id можно domain, вместо frame — ref или slug) кладёт разметку и файлы фрейма этого сайта в библиотеку новой версией, а сам фрейм на этом сайте делает системным. Фрейм с тем же id на других сайтах своего контура, где он системный, получает эту версию; где он свой — остаётся как был (custom_sites). Ответ — как у новой версии системного фрейма, dry_run и bake — тоже. Пока назначен эталон, источником может быть только он.
curl -s -X POST "$API/frames/system" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"blog_id": 268, "frame": "site__header", "dry_run": true}'
Отказы: 403 insufficient_role, 400 blog_required (нет ни blog_id, ни domain), 400 frame_ref_required (нет frame), 404 frame_not_found (на сайте нет такого фрейма), 409 not_base_blog (эталон назначен, а сайт — не он; base_blog, blog_id), 409 wrong_realm (сайт другого контура), 413 frame_too_large.
Поставить фрейм библиотеки на сайт
Новая версия раскатывается только туда, где фрейм уже есть. Новый фрейм, заведённый на эталоне, до существующих сайтов сам не доходит — его ставят этой ручкой. На сайте появляется запись с тем же id, системная, с разметкой и файлами библиотеки; страницы, где он выводится, пересобираются, кэш снимается.
| Поле | Тип | Смысл |
|---|---|---|
blog_id | int | сайт; вместо него можно domain |
all | bool | true — все сайты своего контура, где есть хотя бы один системный фрейм, кроме эталона |
replace_custom | bool | у сайта свой фрейм с тем же слагом — заменить его системным: id меняется, привязки страниц переезжают на новый id, ссылки по слагу продолжают работать |
dry_run | bool | только отчёт |
bake | bool | пересобрать страницы (по умолчанию true) |
curl -s -X POST "$API/frames/system/docs-proba__files/install" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"blog_id": 233}'
{
"success": true,
"applied": true,
"frame": { "id": "380c8a1119a967ca", "slug": "docs-proba__files", "version": 2, "…": "…" },
"sites": [
{ "blog_id": 233, "slug": "docs-proba__files", "status": "installed", "bake": "ok", "cache": { "status": "nothing", "reason": "no page prints this frame — there is no page cache to drop" } }
]
}
sites[].status:
| Статус | Смысл |
|---|---|
installed | фрейм поставлен |
replaced | свой фрейм с тем же слагом заменён системным; replaced: {id, slug} — каким, pages_moved — сколько привязок страниц переехало |
already_system | фрейм уже стоит системным, ничего не сделано |
already_custom | фрейм с этим id стоит своим; hint — вернуть его к библиотеке сбросом POST /frames/{ref}/reset |
error | с all: true — сайт не удалось обработать (error, message), остальные идут дальше |
Фреймы, которые поставленный фрейм вкладывает и которых на сайте нет, ставятся той же ручкой следом — строками в sites с because: "inserted by <слаг>".
Отказы: 403 insufficient_role, 400 blog_required (нет ни blog_id, ни all), 404 system_frame_not_found, 409 system_frame_missing, 409 base_blog (сайт — эталон), 409 slug_taken_by_custom (у сайта свой фрейм с этим слагом; id, library_id — повторите с replace_custom: true), 409 slug_taken_by_system (слаг занят другим системным фреймом), 409 wrong_realm (сайт другого контура).
Убрать из библиотеки
DELETE /frames/system/{ref} убирает фрейм из библиотеки. Сначала каждый сайт своего контура, где фрейм системный, отпускается: фрейм становится своим с той же разметкой и файлами, что были в библиотеке, страницы выводятся как прежде, кэш их страниц снимается. Только потом из библиотеки уходят запись и разметка.
| Поле | Смысл |
|---|---|
blog_id или domain | необязательно; если передан при назначенном эталоне — должен быть эталоном |
dry_run | только отчёт; можно в строке запроса |
curl -s -X DELETE "$API/frames/system/docs-proba__inner" -H "Authorization: Bearer $OP_TOKEN"
{
"success": true,
"applied": true,
"removed": { "id": "488fe4aaf5894677", "slug": "docs-proba__inner", "version": 1, "…": "…" },
"detached_sites": [
{ "blog_id": 233, "slug": "docs-proba__inner", "cache": { "status": "ok", "scope": "pages", "pages": 1 } }
]
}
detached_sites — отпущенные сайты (у dry_run — без cache), skipped_other_contour — сайты другого контура, где фрейм системный: их отпустит только сервис их контура. Пока эталон назначен, фрейм сначала удаляют с самого эталона (DELETE /site/{siteId}/frames/{ref}), иначе — 409 frame_on_base.
Отказы: 403 insufficient_role, 404 system_frame_not_found, 409 not_base_blog (blog_id — не эталон), 409 frame_on_base (фрейм ещё стоит на эталоне; base_blog, id, slug), 409 system_frame_missing.
Снятые ручки
GET, PUT и любые другие методы /frames/defaults, а также POST /frames/{ref}/default отвечают 410 gone: умолчаний и типов у фреймов нет, страница называет фрейм сама.