Доступ, роли и выбор сайта
Каждый запрос к API v2 проходит одну и ту же проверку: кто вы (токен или сессия дашборда), к какому сайту обращаетесь и администратор ли вы на нём. Только после этого запрос попадает в ручку. Всё, что описано на этой странице, одинаково для всех эндпоинтов.
Токен API
Токен — op_ и 64 шестнадцатеричных символа, например op_ВАШ_ТОКЕН в примерах этой документации. У пользователя ровно один токен.
Токен не имеет своих прав. Он наследует права владельца целиком, как пароль учётной записи: работает на всех сайтах, где вы администратор. Скоупов, ограничений по сайтам и токенов «только на чтение» нет. Храните его как пароль.
Где взять. Дашборд https://onpress.pro/dashboard/ → меню пользователя → «Настройки» → «API-ключи» (прямой адрес https://onpress.pro/dashboard/settings/api) → блок «Токен API» → «Выпустить токен» (если токен уже есть — «Выпустить заново»). Токен хранится зашифрованным, и владелец может получить его снова в любой момент (GET /me/api-token) — так кнопка «Промпт для AI» в дашборде подставляет в задание настоящий токен. Это касается токенов, выпущенных с 1 октября 2026 года; у более старых сохранён только хеш, их нужно один раз выпустить заново. В блоке видны префикс (первые символы), дата выпуска и время последнего обращения.
Через API. Тем же, что уже авторизован (сессией дашборда или действующим токеном):
# показать свой действующий токен (no-store; 409 token_not_recoverable у токена до 01.10.2026, 404 token_missing — токена нет)
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/me/api-token" | jq -r .token
# выпустить заново: в ответе поле token — новый токен
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" "$API/me/api-token" | jq '{prefix: .api_token.prefix, token}'
# отозвать: токена у пользователя не остаётся
curl -s -X DELETE -H "Authorization: Bearer $OP_TOKEN" "$API/me/api-token" | jq '{revoked, api_token}'
В дашборде отзыв — там же, блок «Отзыв токена» → «Отозвать». Отозвать токен тем же токеном через API можно: он перестанет работать, как только запрос завершится.
Перевыпуск убивает прежний токен сразу. Всё, что им подписывалось — агенты, скрипты, CI, — перестанет работать на следующем же запросе. Перед перевыпуском убедитесь, что знаете, кто ещё пользуется токеном этой учётной записи.
Как предъявлять. Заголовком Authorization: Bearer op_… (основной способ) или X-OnPress-Key: op_….
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/frames" | jq .total
curl -s -H "X-OnPress-Key: $OP_TOKEN" "$API/site/$SITE/frames" | jq .total
Кэш проверки. Сервис запоминает проверенный токен на минуту. Новый токен работает сразу. Перевыпуск и отзыв через /me/api-token сбрасывают этот кэш, но только в том процессе API, который обработал запрос. Поэтому старый токен может проходить ещё до минуты — не полагайтесь на мгновенный отзыв. Время последнего обращения (last_used_at) обновляется не чаще раза в минуту.
Сессия дашборда
Дашборд ходит в тот же API не токеном, а сессионной кукой (вход через Firebase: почта с паролем или Google). Для API это равноправный способ: ручки принимают и куку, и токен, а запрос с кукой дополнительно проверяется по Origin — с чужого сайта придёт 403 cross_origin. Агентам и скриптам кука не нужна. Подробно — в дашборде и входе.
Роли
Роль одна — администратор сайта, промежуточных нет. Администратор может всё на своих сайтах: чтение и запись страниц, фреймов, данных, меню, стилей, настроек, языков, SEO, медиа, пользователей сайта. Вы работаете со своими сайтами; на чужой сайт API отвечает 404 site_not_found.
Членство в сайте без роли администратора не даёт ничего. Участник сайта без прав администратора получает 403 insufficient_role на любой ручке сайта, включая чтение.
Главный сайт сети (onpress.pro) не исключение: писать в него может только его администратор. Каждый пользователь сети на главном сайте — подписчик, поэтому свои сайты он администрирует, а главный — нет.
Как выбрать сайт
Основной способ: сайт в пути
https://onpress.pro/api/v2/site/{siteId}/…
siteId — девять знаков из цифр 2–9 и латиницы без похожих букв (нет 0, O, o, 1, l, I), например x9HTLHECq. Выдаётся один раз при создании сайта и не меняется никогда, поэтому его можно хранить в скриптах и закладках. Он случайный: по одному идентификатору нельзя вычислить соседний.
Под префиксом живут все эндпоинты одного сайта: /pages (и /pages/{ref}/translations), /frames, /data, /menus, /media, /users, /docs/sidebar, /cache/flush, /seo, /seo/pages. Настройки, главная страница и языки сайта — системные данные /data/site.settings, /data/site.front_page и /data/site.languages.
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/system" | jq '.writable'
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/site.settings" | jq '.items[0].name'
Ответ называет сайт полем site_id и заголовком X-OnPress-Site-Id.
Несуществующий и чужой сайт отвечают одинаково: 404 site_not_found с одним и тем же текстом, будь идентификатор выдуман или принадлежи он сайту, где вы не администратор. Так сеть нельзя перечислить подбором.
Ручки сети
/sites, /me, /auth, /health к сайту не относятся и префикса не имеют. /sites/{id} принимает в пути site_id или домен сайта.
Как узнать siteId
GET $API/sites/{домен}— карточка сайта, полеsite_id.POST $API/sitesпри создании сайта возвращаетsite_idсразу.- В дашборде раздел «Данные» показывает адрес ручек сайта с
site_id.
Лимиты
| Что | Предел | Ответ при превышении |
|---|---|---|
| Запросы к API по токену | 120 в минуту на пользователя, скользящее окно | 429 too_many_requests и Retry-After |
| Запросы из дашборда | 120 в минуту на сессию входа, отдельно от токена | 429 too_many_requests и Retry-After |
| Неудачные входы (кривой или неизвестный токен) | 20 в минуту с одного адреса | 429 too_many_auth_failures и Retry-After |
| Попытки входа в дашборд | 30 в минуту с одного адреса | 429 too_many_requests |
| Тело запроса | 10 МБ | 413 body_too_large |
| Разметка одного фрейма | 2 МБ | 413 frame_too_large |
| Один набор данных | 512 КБ | 413 dataset_too_large |
| Собственный CSS сайта | 256 КБ | 413 css_too_large |
| Файл медиа | 15 МБ (base64 в теле — примерно до 7 МБ файла) | 413 file_too_large |
| Список страниц | до 200 на страницу выдачи | больше — урезается |
| Список медиа | до 100 на страницу выдачи | больше — урезается |
Каждый ответ авторизованной ручки несёт X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset (unix-время, когда освободится самый старый запрос окна). Лимит токена считается на пользователя, а не на сам токен: перевыпуск токена счётчик не обнуляет. У каждой сессии дашборда своё окно, не общее с токеном: скрипт или агент, который выбрал лимит токена, не мешает работать в дашборде тому же пользователю, и наоборот. Счётчик живёт в памяти процесса API — после перезапуска сервиса он обнуляется, это защита от случайного флуда, а не квота.
Форма ответов
Успех — объект с "success": true и полями ручки. На адресе с /site/{siteId}/ в нем есть site_id.
Ошибка ручки — объект с "success": false, машинным кодом в error, пояснением в message и дополнительными полями конкретной ошибки:
{
"success": false,
"error": "unknown_language",
"message": "language \"de\" does not exist on this site; the site has en, ru",
"lang": "de",
"languages": ["en", "ru"]
}
Ошибка до ручки — авторизация, выбор сайта, неразборчивое тело, несуществующий маршрут — приходит в другой форме — с code вместо error:
{ "code": "site_not_found", "message": "there is no site AAAAAAAAA you can reach with these credentials", "data": { "status": 404 } }
Ветвитесь по error (или code) и HTTP-статусу, а не по тексту: тексты на английском и могут уточняться. Коды, которые могут прийти на любой запрос:
| Код | HTTP | Что значит |
|---|---|---|
missing_token | 401 | нет ни Authorization: Bearer, ни X-OnPress-Key |
invalid_token | 401 | токен неизвестен, отозван или не той формы |
too_many_auth_failures | 429 | слишком много неудачных входов с вашего адреса |
cross_origin | 403 | запрос с кукой сессии пришёл не с адреса дашборда |
site_not_found | 404 | сайта нет, он не ваш, или домен не из сети |
insufficient_role | 403 | вы не администратор выбранного сайта |
wrong_realm | 409 | запись пришла не в тот контур (см. ниже), ничего не записано |
too_many_requests | 429 | превышен лимит 120 запросов в минуту |
invalid_json | 400 | тело не разбирается как JSON |
body_too_large | 413 | тело больше 10 МБ |
not_found | 404 | нет такого маршрута |
internal_error | 500 | сбой сервиса, подробности в его журнале |
db_error | 500 | база отказала в записи; в db_code код драйвера |
internal_unavailable | 502 | исполнитель на стороне сайта не ответил или не установлен |
Полная таблица по всем ручкам — в кодах ошибок.
Контуры
У одной базы данных два экземпляра API: боевой https://onpress.pro/api/v2/ и внутренний стенд. Боевой работает со всеми сайтами без ограничений — вам нужен только он. Контур видно без записи: GET $API/health отвечает "realm": "prod", а каждый ответ ручки сайта несёт заголовок X-OnPress-Realm. Если запись всё же попала в чужой контур, она отклоняется 409 wrong_realm, в ответе поля blog_realm, instance_realm и адрес, куда слать запрос; ничего не записано.
Проверка себя
curl -s -D - -o /dev/null -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/menus" | grep -i '^x-'
x-onpress-site-id: x9HTLHECq
x-onpress-realm: prod
x-ratelimit-limit: 120
x-ratelimit-remaining: 91
x-ratelimit-reset: 1790368006